diff --git a/lib/TinyGPSPlus-1.0.2/src/TinyGPS++.h b/lib/TinyGPSPlus-1.0.2/src/TinyGPS++.h index df29ac49b0..580c77dd7b 100644 --- a/lib/TinyGPSPlus-1.0.2/src/TinyGPS++.h +++ b/lib/TinyGPSPlus-1.0.2/src/TinyGPS++.h @@ -1,362 +1,365 @@ -/* -TinyGPS++ - a small GPS library for Arduino providing universal NMEA parsing -Based on work by and "distanceBetween" and "courseTo" courtesy of Maarten Lamers. -Suggestion to add satellites, courseTo(), and cardinal() by Matt Monson. -Location precision improvements suggested by Wayne Holder. -Copyright (C) 2008-2013 Mikal Hart -All rights reserved. - -This library is free software; you can redistribute it and/or -modify it under the terms of the GNU Lesser General Public -License as published by the Free Software Foundation; either -version 2.1 of the License, or (at your option) any later version. - -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public -License along with this library; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -*/ - -#ifndef __TinyGPSPlus_h -#define __TinyGPSPlus_h - -#if defined(ARDUINO) && ARDUINO >= 100 -#include "Arduino.h" -#else -#include "WProgram.h" -#endif -#include - -#define _GPS_VERSION "1.0.2" // software version of this library -#define _GPS_MPH_PER_KNOT 1.15077945 -#define _GPS_MPS_PER_KNOT 0.51444444 -#define _GPS_KMPH_PER_KNOT 1.852 -#define _GPS_MILES_PER_METER 0.00062137112 -#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 -{ - uint16_t deg; - uint32_t billionths; - bool negative; -public: - RawDegrees() : deg(0), billionths(0), negative(false) - {} -}; - -enum FixQuality { Invalid = 0, GPS = 1, DGPS = 2, PPS = 3, RTK = 4, FloatRTK = 5, Estimated = 6, Manual = 7, Simulated = 8 }; -enum FixMode { - N = 'N', // None - A = 'A', // Autonomous - D = 'D', // Differential - E = 'E'};// Dead Reckoning - -struct TinyGPSLocation -{ - 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; } - const RawDegrees &rawLat() { updated = false; return rawLatData; } - const RawDegrees &rawLng() { updated = false; return rawLngData; } - double lat(); - double lng(); - FixQuality Quality() { /* updated = false; */ return fixQuality; } - FixMode Mode() { /* updated = false; */ return fixMode; } - - TinyGPSLocation() : valid(false), updated(false), lastCommitTime(0), fixQuality(Invalid), newFixQuality(Invalid), fixMode(N), newFixMode(N) - {} - -private: - bool valid, updated; - RawDegrees rawLatData, rawLngData, rawNewLatData, rawNewLngData; - uint32_t lastCommitTime; - void commit(); - void setLatitude(const char *term); - void setLongitude(const char *term); - FixQuality fixQuality, newFixQuality; - FixMode fixMode, newFixMode; -}; - -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), lastCommitTime(0) - {} - - 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; -public: - bool isValid() const { return valid; } - bool isUpdated() const { return updated; } - uint32_t age() const { return valid ? millis() - lastCommitTime : (uint32_t)ULONG_MAX; } - - uint32_t value() { updated = false; return date; } - uint16_t year(); - uint8_t month(); - uint8_t day(); - - TinyGPSDate() : valid(false), updated(false), date(0), newDate(0), lastCommitTime(0) - {} - -private: - bool valid, updated; - uint32_t date, newDate; - uint32_t lastCommitTime; - void commit(); - void setDate(const char *term); -}; - -struct TinyGPSTime -{ - 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; } - - uint32_t value() { updated = false; return time; } - uint8_t hour(); - uint8_t minute(); - uint8_t second(); - uint8_t centisecond(); - - TinyGPSTime() : valid(false), updated(false), time(0), newTime(0), lastCommitTime(0) - {} - -private: - bool valid, updated; - uint32_t time, newTime; - uint32_t lastCommitTime; - void commit(); - void setTime(const char *term); -}; - -struct TinyGPSDecimal -{ - 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; } - int32_t value() { updated = false; return val; } - - TinyGPSDecimal() : valid(false), updated(false), lastCommitTime(0), val(0), newval(0) - {} - -private: - bool valid, updated; - uint32_t lastCommitTime; - int32_t val, newval; - void commit(); - void set(const char *term); -}; - -struct TinyGPSInteger -{ - 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; } - uint32_t value() { updated = false; return val; } - - TinyGPSInteger() : valid(false), updated(false), lastCommitTime(0), val(0), newval(0) - {} - -private: - bool valid, updated; - uint32_t lastCommitTime; - uint32_t val, newval; - void commit(); - void set(const char *term); -}; - -struct TinyGPSSpeed : TinyGPSDecimal -{ - double knots() { return value() / 100.0; } - double mph() { return _GPS_MPH_PER_KNOT * value() / 100.0; } - double mps() { return _GPS_MPS_PER_KNOT * value() / 100.0; } - double kmph() { return _GPS_KMPH_PER_KNOT * value() / 100.0; } -}; - -struct TinyGPSCourse : public TinyGPSDecimal -{ - double deg() { return value() / 100.0; } -}; - -struct TinyGPSAltitude : TinyGPSDecimal -{ - double meters() { return value() / 100.0; } - double miles() { return _GPS_MILES_PER_METER * value() / 100.0; } - double kilometers() { return _GPS_KM_PER_METER * value() / 100.0; } - double feet() { return _GPS_FEET_PER_METER * value() / 100.0; } -}; - -struct TinyGPSHDOP : TinyGPSDecimal -{ - double hdop() { return value() / 100.0; } -}; - -class TinyGPSPlus; -class TinyGPSCustom -{ -public: - TinyGPSCustom() {}; - TinyGPSCustom(TinyGPSPlus &gps, const char *sentenceName, int termNumber); - void begin(TinyGPSPlus &gps, const char *_sentenceName, int _termNumber); - - bool isUpdated() const { return updated; } - bool isValid() const { return valid; } - uint32_t age() const { return valid ? millis() - lastCommitTime : (uint32_t)ULONG_MAX; } - const char *value() { updated = false; return buffer; } - -private: - void commit(); - void set(const char *term); - - char stagingBuffer[_GPS_MAX_FIELD_SIZE + 1] = {0}; - char buffer[_GPS_MAX_FIELD_SIZE + 1] = {0}; - unsigned long lastCommitTime = 0; - bool valid = false; - bool updated = false; - const char *sentenceName = nullptr; - int termNumber = 0; - friend class TinyGPSPlus; - TinyGPSCustom *next = nullptr; -}; - -class TinyGPSPlus -{ -public: - TinyGPSPlus(); - bool encode(char c); // process one character received from GPS - TinyGPSPlus &operator << (char c) {encode(c); return *this;} - - TinyGPSLocation location; - TinyGPSDate date; - TinyGPSTime time; - TinyGPSSpeed speed; - TinyGPSCourse course; - TinyGPSAltitude altitude; - TinyGPSInteger satellites; - TinyGPSHDOP hdop; - TinyGPSSatellites satellitesStats; - - static const char *libraryVersion() { return _GPS_VERSION; } - - static double distanceBetween(double lat1, double long1, double lat2, double long2); - static double courseTo(double lat1, double long1, double lat2, double long2); - static const char *cardinal(float course); - - static int32_t parseDecimal(const char *term); - static void parseDegrees(const char *term, RawDegrees °); - - uint32_t charsProcessed() const { return encodedCharCount; } - uint32_t sentencesWithFix() const { return sentencesWithFixCount; } - uint32_t failedChecksum() const { return failedChecksumCount; } - uint32_t passedChecksum() const { return passedChecksumCount; } - uint32_t invalidData() const { return invalidDataCount; } - -private: - 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_GPGLL, // GLL - Latitude and longitude, with time of position fix and status - - GPS_SENTENCE_GPTXT, // Free format TXT field - - 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 = 0; - bool isChecksumTerm = false; - char term[_GPS_MAX_FIELD_SIZE] = {0}; - uint8_t curSentenceType = 0; - uint8_t curSentenceSystem = 0; - uint8_t curTermNumber = 0; - uint8_t curTermOffset = 0; - bool sentenceHasFix = false; - - // custom element support - friend class TinyGPSCustom; - TinyGPSCustom *customElts = nullptr; - TinyGPSCustom *customCandidates = nullptr; - void insertCustom(TinyGPSCustom *pElt, const char *sentenceName, int index); - - // statistics - uint32_t encodedCharCount = 0; - uint32_t sentencesWithFixCount = 0; - uint32_t failedChecksumCount = 0; - uint32_t passedChecksumCount = 0; - uint32_t invalidDataCount = 0; - - // internal utilities - int fromHex(char a); - bool endOfTermHandler(); -}; - -#endif // def(__TinyGPSPlus_h) +/* +TinyGPS++ - a small GPS library for Arduino providing universal NMEA parsing +Based on work by and "distanceBetween" and "courseTo" courtesy of Maarten Lamers. +Suggestion to add satellites, courseTo(), and cardinal() by Matt Monson. +Location precision improvements suggested by Wayne Holder. +Copyright (C) 2008-2013 Mikal Hart +All rights reserved. + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef __TinyGPSPlus_h +#define __TinyGPSPlus_h + +#if defined(ARDUINO) && ARDUINO >= 100 +#include "Arduino.h" +#else +#include "WProgram.h" +#endif +#include + +#define _GPS_VERSION "1.0.2" // software version of this library +#define _GPS_MPH_PER_KNOT 1.15077945 +#define _GPS_MPS_PER_KNOT 0.51444444 +#define _GPS_KMPH_PER_KNOT 1.852 +#define _GPS_MILES_PER_METER 0.00062137112 +#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 +{ + uint16_t deg; + uint32_t billionths; + bool negative; +public: + RawDegrees() : deg(0), billionths(0), negative(false) + {} +}; + +enum FixQuality { Invalid = 0, GPS = 1, DGPS = 2, PPS = 3, RTK = 4, FloatRTK = 5, Estimated = 6, Manual = 7, Simulated = 8 }; +enum FixMode { + N = 'N', // None + A = 'A', // Autonomous + D = 'D', // Differential + E = 'E'};// Dead Reckoning + +struct TinyGPSLocation +{ + 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; } + const RawDegrees &rawLat() { updated = false; return rawLatData; } + const RawDegrees &rawLng() { updated = false; return rawLngData; } + double lat(); + double lng(); + FixQuality Quality() { /* updated = false; */ return fixQuality; } + FixMode Mode() { /* updated = false; */ return fixMode; } + + TinyGPSLocation() : valid(false), updated(false), lastCommitTime(0), fixQuality(Invalid), newFixQuality(Invalid), fixMode(N), newFixMode(N) + {} + +private: + bool valid, updated; + RawDegrees rawLatData, rawLngData, rawNewLatData, rawNewLngData; + uint32_t lastCommitTime; + void commit(); + void setLatitude(const char *term); + void setLongitude(const char *term); + FixQuality fixQuality, newFixQuality; + FixMode fixMode, newFixMode; +}; + +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), lastCommitTime(0) + {} + + 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; +public: + bool isValid() const { return valid; } + bool isUpdated() const { return updated; } + uint32_t age() const { return valid ? millis() - lastCommitTime : (uint32_t)ULONG_MAX; } + + uint32_t value() { updated = false; return date; } + uint16_t year(); + uint8_t month(); + uint8_t day(); + + TinyGPSDate() : valid(false), updated(false), date(0), newDate(0), lastCommitTime(0) + {} + +private: + bool valid, updated; + uint32_t date, newDate; + uint32_t lastCommitTime; + void commit(); + void setDate(const char *term); +}; + +struct TinyGPSTime +{ + 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; } + + uint32_t value() { updated = false; return time; } + uint8_t hour(); + uint8_t minute(); + uint8_t second(); + uint8_t centisecond(); + + TinyGPSTime() : valid(false), updated(false), time(0), newTime(0), lastCommitTime(0) + {} + +private: + bool valid, updated; + uint32_t time, newTime; + uint32_t lastCommitTime; + void commit(); + void setTime(const char *term); +}; + +struct TinyGPSDecimal +{ + 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; } + int32_t value() { updated = false; return val; } + + TinyGPSDecimal() : valid(false), updated(false), lastCommitTime(0), val(0), newval(0) + {} + +private: + bool valid, updated; + uint32_t lastCommitTime; + int32_t val, newval; + void commit(); + void set(const char *term); +}; + +struct TinyGPSInteger +{ + 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; } + uint32_t value() { updated = false; return val; } + + TinyGPSInteger() : valid(false), updated(false), lastCommitTime(0), val(0), newval(0) + {} + +private: + bool valid, updated; + uint32_t lastCommitTime; + uint32_t val, newval; + void commit(); + void set(const char *term); +}; + +struct TinyGPSSpeed : TinyGPSDecimal +{ + double knots() { return value() / 100.0; } + double mph() { return _GPS_MPH_PER_KNOT * value() / 100.0; } + double mps() { return _GPS_MPS_PER_KNOT * value() / 100.0; } + double kmph() { return _GPS_KMPH_PER_KNOT * value() / 100.0; } +}; + +struct TinyGPSCourse : public TinyGPSDecimal +{ + double deg() { return value() / 100.0; } +}; + +struct TinyGPSAltitude : TinyGPSDecimal +{ + double meters() { return value() / 100.0; } + double miles() { return _GPS_MILES_PER_METER * value() / 100.0; } + double kilometers() { return _GPS_KM_PER_METER * value() / 100.0; } + double feet() { return _GPS_FEET_PER_METER * value() / 100.0; } +}; + +struct TinyGPSHDOP : TinyGPSDecimal +{ + double hdop() { return value() / 100.0; } +}; + +class TinyGPSPlus; +class TinyGPSCustom +{ +public: + TinyGPSCustom() {}; + TinyGPSCustom(TinyGPSPlus &gps, const char *sentenceName, int termNumber); + void begin(TinyGPSPlus &gps, const char *_sentenceName, int _termNumber); + + bool isUpdated() const { return updated; } + bool isValid() const { return valid; } + uint32_t age() const { return valid ? millis() - lastCommitTime : (uint32_t)ULONG_MAX; } + const char *value() { updated = false; return buffer; } + +private: + void commit(); + void set(const char *term); + + char stagingBuffer[_GPS_MAX_FIELD_SIZE + 1] = {0}; + char buffer[_GPS_MAX_FIELD_SIZE + 1] = {0}; + unsigned long lastCommitTime = 0; + bool valid = false; + bool updated = false; + const char *sentenceName = nullptr; + int termNumber = 0; + friend class TinyGPSPlus; + TinyGPSCustom *next = nullptr; +}; + +class TinyGPSPlus +{ +public: + TinyGPSPlus(); + bool encode(char c); // process one character received from GPS + TinyGPSPlus &operator << (char c) {encode(c); return *this;} + + TinyGPSLocation location; + TinyGPSDate date; + TinyGPSTime time; + TinyGPSSpeed speed; + TinyGPSCourse course; + TinyGPSAltitude altitude; + TinyGPSInteger satellites; + TinyGPSHDOP hdop; + TinyGPSSatellites satellitesStats; + + static const char *libraryVersion() { return _GPS_VERSION; } + + static double distanceBetween(double lat1, double long1, double lat2, double long2); + static double courseTo(double lat1, double long1, double lat2, double long2); + static const char *cardinal(float course); + + static int32_t parseDecimal(const char *term); + static void parseDegrees(const char *term, RawDegrees °); + + uint32_t charsProcessed() const { return encodedCharCount; } + uint32_t sentencesWithFix() const { return sentencesWithFixCount; } + uint32_t failedChecksum() const { return failedChecksumCount; } + uint32_t passedChecksum() const { return passedChecksumCount; } + uint32_t invalidData() const { return invalidDataCount; } + +public: + enum GPS_Sentence_type { + 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_GPGLL, // GLL - Latitude and longitude, with time of position fix and status + + GPS_SENTENCE_GPTXT, // Free format TXT field + + GPS_SENTENCE_OTHER}; + + GPS_Sentence_type getCurrentSentenceType() const { return static_cast(curSentenceType); } + +private: + 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 = 0; + bool isChecksumTerm = false; + char term[_GPS_MAX_FIELD_SIZE] = {0}; + uint8_t curSentenceType = 0; + uint8_t curSentenceSystem = 0; + uint8_t curTermNumber = 0; + uint8_t curTermOffset = 0; + bool sentenceHasFix = false; + + // custom element support + friend class TinyGPSCustom; + TinyGPSCustom *customElts = nullptr; + TinyGPSCustom *customCandidates = nullptr; + void insertCustom(TinyGPSCustom *pElt, const char *sentenceName, int index); + + // statistics + uint32_t encodedCharCount = 0; + uint32_t sentencesWithFixCount = 0; + uint32_t failedChecksumCount = 0; + uint32_t passedChecksumCount = 0; + uint32_t invalidDataCount = 0; + + // internal utilities + int fromHex(char a); + bool endOfTermHandler(); +}; + +#endif // def(__TinyGPSPlus_h) diff --git a/src/_P082_GPS.ino b/src/_P082_GPS.ino index 143576107a..569d607b6c 100644 --- a/src/_P082_GPS.ino +++ b/src/_P082_GPS.ino @@ -31,10 +31,6 @@ -// Must use volatile declared variable (which will end up in iRAM) -volatile unsigned long P082_pps_time = 0; -void Plugin_082_interrupt() IRAM_ATTR; - boolean Plugin_082(uint8_t function, struct EventStruct *event, String& string) { boolean success = false; @@ -329,14 +325,10 @@ boolean Plugin_082(uint8_t function, struct EventStruct *event, String& string) return success; } - if (P082_data->init(port, serial_rx, serial_tx)) { + if (P082_data->init(port, serial_rx, serial_tx, pps_pin)) { success = true; serialHelper_log_GpioDescription(port, serial_rx, serial_tx); - if (validGpio(pps_pin)) { - // pinMode(pps_pin, INPUT_PULLUP); - attachInterrupt(pps_pin, Plugin_082_interrupt, RISING); - } # ifdef P082_USE_U_BLOX_SPECIFIC P082_data->setPowerMode(static_cast(P082_POWER_MODE)); P082_data->setDynamicModel(static_cast(P082_DYNAMIC_MODEL)); @@ -751,6 +743,12 @@ void P082_html_show_stats(struct EventStruct *event) { chksumStats += P082_data->gps->invalidData(); addHtml(chksumStats); } +#ifndef BUILD_NO_DEBUG +/* + addRowLabel(F("SW PPS stats")); + addHtml(P082_data->getPPSStats()); +*/ +#endif } void P082_setSystemTime(struct EventStruct *event) { @@ -761,14 +759,7 @@ void P082_setSystemTime(struct EventStruct *event) { return; } - P082_data->_pps_time = P082_pps_time; // Must copy the interrupt gathered time first. - P082_pps_time = 0; - P082_data->tryUpdateSystemTime(); } -void Plugin_082_interrupt() { - P082_pps_time = millis(); -} - #endif // USES_P082 diff --git a/src/src/DataTypes/ESPEasyTimeSource.cpp b/src/src/DataTypes/ESPEasyTimeSource.cpp index 37427c1259..69cb865edb 100644 --- a/src/src/DataTypes/ESPEasyTimeSource.cpp +++ b/src/src/DataTypes/ESPEasyTimeSource.cpp @@ -7,6 +7,7 @@ const __FlashStringHelper* toString(timeSource_t timeSource) switch (timeSource) { case timeSource_t::GPS_PPS_time_source: return F("GPS PPS"); case timeSource_t::GPS_time_source: return F("GPS"); + case timeSource_t::GPS_time_source_no_fix: return F("GPS no Fix"); case timeSource_t::NTP_time_source: return F("NTP"); case timeSource_t::Manual_set: return F("Manual"); case timeSource_t::ESP_now_peer: return F(ESPEASY_NOW_NAME " peer"); @@ -27,6 +28,7 @@ bool isExternalTimeSource(timeSource_t timeSource) switch (timeSource) { case timeSource_t::GPS_PPS_time_source: case timeSource_t::GPS_time_source: + case timeSource_t::GPS_time_source_no_fix: case timeSource_t::NTP_time_source: case timeSource_t::External_RTC_time_source: case timeSource_t::Manual_set: @@ -36,9 +38,9 @@ bool isExternalTimeSource(timeSource_t timeSource) } } -// Typical time wander for ESP nodes is 0.04 ms/sec -// Meaning per 25 sec, the time may wander 1 msec. -#define TIME_WANDER_FACTOR 25000 +// Typical time wander for ESP nodes should be less than 10 ppm +// Meaning per hour, the time should wander less than 36 msec. +#define TIME_WANDER_FACTOR 100000 uint32_t updateExpectedWander( int32_t current_wander, @@ -68,6 +70,12 @@ uint32_t computeExpectedWander(timeSource_t timeSource, expectedWander_ms += 10; break; } + case timeSource_t::GPS_time_source_no_fix: + { + // When the GPS has no fix, the reported time may differ quite a bit + expectedWander_ms += 2000; + break; + } case timeSource_t::NTP_time_source: { // Typical time needed to perform a NTP request to an online NTP server @@ -78,8 +86,8 @@ uint32_t computeExpectedWander(timeSource_t timeSource, case timeSource_t::ESP_now_peer: case timeSource_t::ESPEASY_p2p_UDP: { - // expected wander is 144 per hour. - // Using a 'penalty' of 1000 makes it only preferrable over NTP after +/- 7 hour. + // expected wander is 36 per hour. + // Using a 'penalty' of 1000 makes it only preferrable over NTP after +/- 28 hour. expectedWander_ms += 1000; break; } diff --git a/src/src/DataTypes/ESPEasyTimeSource.h b/src/src/DataTypes/ESPEasyTimeSource.h index 565753282e..def9bb803e 100644 --- a/src/src/DataTypes/ESPEasyTimeSource.h +++ b/src/src/DataTypes/ESPEasyTimeSource.h @@ -9,7 +9,7 @@ class String; -#define EXT_TIME_SOURCE_MIN_UPDATE_INTERVAL_MSEC 3600000 +#define EXT_TIME_SOURCE_MIN_UPDATE_INTERVAL_MSEC 1800000 #define EXT_TIME_SOURCE_MIN_UPDATE_INTERVAL_SEC 3600 // Time Source type, sort by priority. @@ -28,6 +28,7 @@ enum class timeSource_t : uint8_t { ESP_now_peer = 40, // < 5 msec accuracy between nodes, but time on the whole network may drift ESPEASY_p2p_UDP = 41, External_RTC_time_source = 45, // Typically +/- 500 msec off. + GPS_time_source_no_fix = 46, // Typically 500 - 1000 msec off. Restore_RTC_time_source = 50, // > 1 sec difference per reboot No_time_source = 255 // No time set diff --git a/src/src/Helpers/ESPEasy_time.cpp b/src/src/Helpers/ESPEasy_time.cpp index 3894116571..3a26c249d0 100644 --- a/src/src/Helpers/ESPEasy_time.cpp +++ b/src/src/Helpers/ESPEasy_time.cpp @@ -464,6 +464,7 @@ bool ESPEasy_time::systemTimePresent() const { case timeSource_t::External_RTC_time_source: case timeSource_t::GPS_time_source: case timeSource_t::GPS_PPS_time_source: + case timeSource_t::GPS_time_source_no_fix: case timeSource_t::ESP_now_peer: case timeSource_t::ESPEASY_p2p_UDP: case timeSource_t::Manual_set: diff --git a/src/src/Helpers/ESPEasy_time.h b/src/src/Helpers/ESPEasy_time.h index f85d14357e..d10f219ec7 100644 --- a/src/src/Helpers/ESPEasy_time.h +++ b/src/src/Helpers/ESPEasy_time.h @@ -33,6 +33,8 @@ class ESPEasy_time { void restoreLastKnownUnixTime(unsigned long lastSysTime, uint8_t deepSleepState); + // Set external time source + // Wander is expected error in msec bool setExternalTimeSource_withTimeWander(double new_time, timeSource_t new_timeSource, int32_t wander, diff --git a/src/src/Helpers/ModuloOversamplingHelper.h b/src/src/Helpers/ModuloOversamplingHelper.h new file mode 100644 index 0000000000..9092aecb4d --- /dev/null +++ b/src/src/Helpers/ModuloOversamplingHelper.h @@ -0,0 +1,146 @@ +#ifndef HELPERS_MODULOOVERSAMPLINGHELPER_H +#define HELPERS_MODULOOVERSAMPLINGHELPER_H + +#include "../../ESPEasy_common.h" +#include +#include "../Helpers/OversamplingHelper.h" + +template +class ModuloOversamplingHelper { +public: + + // When working with cyclical values which may 'overflow', + // it is hard to compute an average. + // For example with some angle in degrees, 359 and 1 degree are very close to eachother. + // The expected average would be 0, however the numerical average is 180. + // + // Typical use cases: + // - Compute direction in degrees (range 0 .. 359) + // - Compute 'jitter' in some periodical signal + // - Phase shift calculations + ModuloOversamplingHelper() { + // Just set some value for _modulo + // so there will be no overflow when applying the offset + // And we have a default constructor. + // However it is highly unlikely this is a practical value, + // so should not be run with this default value + _modulo = std::numeric_limits::max() / 2; + } + + ModuloOversamplingHelper(T modulo) : _modulo(modulo) {} + + void setModulo(T modulo) { + _modulo = modulo; + reset(); + } + + // Add new sample + void add(T currentValue) { + // Make sure the value is in range 0 ... (_modulo - 1) + currentValue %= _modulo; + + if (_oversampling.getCount() == 0) { + // Work with values centered around the middle of the modulo range. + // Since the template type T can be unsigned, + // must make sure the offset is positive. + // N.B. _offset is always less than or equal to (_modulo / 2) + const T modulo_halve = (_modulo / 2); + + if (currentValue > modulo_halve) { + _offset = currentValue - modulo_halve; + } else { + _offset = modulo_halve - currentValue; + } + } + + T shiftedValue = currentValue + _offset; // FIXME TD-er: Could overflow + + if (shiftedValue > _modulo) { + shiftedValue -= _modulo; + } + + _oversampling.add(shiftedValue); + } + + // Get current oversampling value without reset. + // @param value Value will only be updated if there were samples available + bool peek(SUM_VALUE_TYPE& value) const { + if (!_oversampling.peek(value)) { + return false; + } + + if (value < _offset) { + value += _modulo; + } + value -= _offset; + return true; + } + + // Get current oversampling value and reset. + // @param value Value will only be updated if there were samples available + bool get(SUM_VALUE_TYPE& value) { + // Must call functions of this class and not just return _oversampling.get() + // This way we are sure the _offset is properly applied + if (peek(value)) { + reset(); + return true; + } + return false; + } + + // Return number of used samples + uint32_t getCount() const { + return _oversampling.getCount(); + } + + // Clear all oversampling values + void reset() { + _oversampling.reset(); + } + + // Clear all oversampling values and add last average if there were samples available. + SUM_VALUE_TYPE resetKeepLast() { + SUM_VALUE_TYPE value{}; + + // Must call functions of this class and not just return _oversampling.resetKeepLast() + // This way we are sure the _offset is properly applied + if (get(value)) { + add(static_cast(value)); + } + return value; + } + + // Clear all oversampling values and add last average if there were samples available. + // Last value will be added with a weight according to the given ratio of the last count. + // @param countRatio New weight will be previous nr of samples / countRatio + void resetKeepLastWeighted(int countRatio) { + SUM_VALUE_TYPE value{}; + const uint32_t count = getCount(); + + // Must call functions of this class and not just return _oversampling.resetKeepLast() + // This way we are sure the _offset is properly applied + if (get(value)) { + if (count > countRatio) { + const uint32_t weight = (count + (countRatio / 2)) / countRatio; + + for (uint32_t i = 0; i < weight; ++i) { + add(static_cast(value)); + } + } else { + add(static_cast(value)); + } + } + } + + void setFilterPeaks(bool enable) { + _oversampling.setFilterPeaks(enable); + } + +private: + + OversamplingHelper_oversampling; + T _modulo; + T _offset{}; +}; + +#endif // ifndef HELPERS_MODULOOVERSAMPLINGHELPER_H diff --git a/src/src/Helpers/OversamplingHelper.cpp b/src/src/Helpers/OversamplingHelper.cpp deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/src/src/PluginStructs/P082_data_struct.cpp b/src/src/PluginStructs/P082_data_struct.cpp index 707669659e..ae34724513 100644 --- a/src/src/PluginStructs/P082_data_struct.cpp +++ b/src/src/PluginStructs/P082_data_struct.cpp @@ -63,6 +63,116 @@ const __FlashStringHelper* toString(P082_DynamicModel model) { return F(""); } +P082_software_pps::P082_software_pps() +{ + for (size_t i = 0; i < NR_ELEMENTS(_second_frac_in_usec); ++i) { + _second_frac_in_usec[i].setModulo(1000000ul); + } +} + +void P082_software_pps::addStartOfSentence(uint32_t bytesAvailableInSerialBuffer) +{ + if (_baudrate == 0) { + return; + } + + // Subtract the time (usec) taken to send the nr of bytes present in the serial buffer + _cur_start_sentence_usec = getMicros64() - bytesToUsec(bytesAvailableInSerialBuffer); +} + +void P082_software_pps::setSentenceType( + TinyGPSPlus::GPS_Sentence_type sentenceType, + uint32_t bytesAvailableInSerialBuffer) +{ + if ((sentenceType < TinyGPSPlus::GPS_SENTENCE_OTHER) && (_cur_start_sentence_usec != 0ull)) { + const uint64_t endOfLine_received_usec = getMicros64() - bytesToUsec(bytesAvailableInSerialBuffer); + const int64_t sentence_duration = timeDiff64(_cur_start_sentence_usec, endOfLine_received_usec); + +// if (usecPassedSince(_cur_start_sentence_usec) < 1000000ll) { + + // Assume a NMEA sentence cannot be over 80 bytes + // Apply some tolerance, thus check for duration to receive 120 bytes + if (sentence_duration < bytesToUsec(120)) { + // Make sure we're not committing a timestamp to the wrong sentence type + // However we only set this when a complete sentence was processed, + // so it is highly unlikely we missed the start of the sentence. + // Only way this can happen is when we missed exactly a complete sentence (or multiple) + _second_frac_in_usec[sentenceType].add(_cur_start_sentence_usec % 1000000ull); + + if (_second_frac_in_usec[sentenceType].getCount() >= 10) { + // Filter out the outliers and seed with current average. + _second_frac_in_usec[sentenceType].resetKeepLast(); + } + } + } + _cur_start_sentence_usec = 0; +} + +void P082_software_pps::setBaudrate(uint32_t baudrate) { + if (baudrate != 0) { + _baudrate = baudrate; + } +} + +bool P082_software_pps::getPPS(uint32_t& second_frac_in_usec) const +{ + /* + // Check the timestamps of the sentences which are likely to only have occurred once per second + constexpr TinyGPSPlus::GPS_Sentence_type sentenceTypes[] = + { + TinyGPSPlus::GPS_SENTENCE_GPRMC, + TinyGPSPlus::GPS_SENTENCE_GPGGA, + TinyGPSPlus::GPS_SENTENCE_GPGLL + }; + */ + + return _second_frac_in_usec[TinyGPSPlus::GPS_SENTENCE_GPRMC].peek(second_frac_in_usec); +} + +uint64_t P082_software_pps::bytesToUsec(uint32_t bytes) const +{ + if (_baudrate == 0) { + return 0ull; + } + + // Assume 10 bits per byte. (8N1) + uint64_t duration_usec = bytes; + + duration_usec *= 10000000ull; + duration_usec /= _baudrate; + return duration_usec; +} + +#ifndef BUILD_NO_DEBUG +String P082_software_pps::getStats() const +{ + String res; + constexpr uint32_t nrelements = NR_ELEMENTS(_second_frac_in_usec); + for (size_t i = 0; i < nrelements; ++i) { + uint32_t value{}; + _second_frac_in_usec[i].peek(value); + { + + switch (i) { + case TinyGPSPlus::GPS_SENTENCE_GPGGA: res += F("GGA"); break; + case TinyGPSPlus::GPS_SENTENCE_GPRMC: res += F("RMC"); break; + case TinyGPSPlus::GPS_SENTENCE_GPGSA: res += F("GSA"); break; + case TinyGPSPlus::GPS_SENTENCE_GPGSV: res += F("GSV"); break; + case TinyGPSPlus::GPS_SENTENCE_GPGLL: res += F("GLL"); break; + case TinyGPSPlus::GPS_SENTENCE_GPTXT: res += F("TXT"); break; + default: + res += F("---"); + break; + } + res += strformat(F(": %06d (%d)
"), value, _second_frac_in_usec[i].getCount()); + } + } + return res; +} +#endif + + + P082_data_struct::P082_data_struct() : gps(nullptr), easySerial(nullptr) { for (size_t i = 0; i < static_cast(P082_query::P082_NR_OUTPUT_OPTIONS); ++i) { _cache[i] = 0.0f; @@ -70,6 +180,10 @@ P082_data_struct::P082_data_struct() : gps(nullptr), easySerial(nullptr) { } P082_data_struct::~P082_data_struct() { + if (validGpio(_ppsPin)) { + detachInterrupt(digitalPinToInterrupt(_ppsPin)); + } + if (gps != nullptr) { delete gps; gps = nullptr; @@ -94,7 +208,11 @@ P082_data_struct::~P082_data_struct() { } } */ -bool P082_data_struct::init(ESPEasySerialPort port, const int16_t serial_rx, const int16_t serial_tx) { +bool P082_data_struct::init( + ESPEasySerialPort port, + const int16_t serial_rx, + const int16_t serial_tx, + const int8_t pps_pin) { if (serial_rx < 0) { return false; } @@ -121,6 +239,20 @@ bool P082_data_struct::init(ESPEasySerialPort port, const int16_t serial_rx, con easySerial->begin(9600); wakeUp(); } + + _ppsPin = pps_pin; + + if (validGpio(_ppsPin)) { + pinMode(_ppsPin, INPUT); + + attachInterruptArg( + digitalPinToInterrupt(_ppsPin), + reinterpret_cast(pps_interrupt), + this, RISING); + } else { + _softwarePPS.setBaudrate(easySerial->getBaudRate()); + } + return isInitialized(); } @@ -212,29 +344,15 @@ bool P082_data_struct::loop() { _currentSentence = String(); # endif // ifdef P082_SEND_GPS_TO_LOG completeSentence = true; + available = easySerial->available(); + _softwarePPS.setSentenceType(gps->getCurrentSentenceType(), available); } else { - if (available == 0) { + if (c == '$') { available = easySerial->available(); + _softwarePPS.addStartOfSentence(available); } - - if (c == '$') { - _start_prev_sentence = _start_sentence; - _start_sentence = millis(); - const unsigned long baudrate = easySerial->getBaudRate(); - - if (baudrate != 0) { - // Subtract the time (msec) taken to send the nr of bytes present in the serial buffer - // Assume 10 bits per byte. (8N1) - _start_sentence -= (available * 10000) / baudrate; - } - const int32_t max_sentence_duration = (160 * 10000) / baudrate; - - if (timeDiff(_start_prev_sentence, _start_sentence) > max_sentence_duration) { - _start_sequence = _start_sentence; - - // Debug accuracy of computing the time stability - // addLog(LOG_LEVEL_INFO, concat(F("GPS : Start Sequence: "), _start_sequence)); - } + if (available == 0) { + available = easySerial->available(); } } } @@ -279,9 +397,9 @@ ESPEASY_RULES_FLOAT_TYPE P082_data_struct::distanceSinceLast(unsigned int maxAge // additional centiseconds given by the GPS. bool P082_data_struct::getDateTime( struct tm& dateTime, + uint8_t & centiseconds, uint32_t & age, - bool & updated, - bool & pps_sync) { + bool & updated) { updated = false; if (!isInitialized()) { @@ -292,27 +410,12 @@ bool P082_data_struct::getDateTime( return false; } - if (_pps_time != 0) { - age = timePassedSince(_pps_time); - _pps_time = 0; - pps_sync = true; - - if ((age > P082_TIMESTAMP_AGE) || (gps->time.age() > age)) { - return false; - } - } else { - age = gps->time.age(); - pps_sync = false; - } + age = gps->time.age(); if (age > P082_TIMESTAMP_AGE) { return false; } - if (!gps->time.isUpdated() || !gps->date.isUpdated()) { - return false; - } - if (gps->date.age() > P082_TIMESTAMP_AGE) { return false; } @@ -337,30 +440,15 @@ bool P082_data_struct::getDateTime( const uint32_t reported_time = gps->time.value(); const uint32_t reported_date = gps->date.value(); - updated = reported_time != _last_time; + // FIXME TD-er: Must the offset in centisecond be added when pps_sync active? + if (!validGpio(_ppsPin)) { + centiseconds = gps->time.centisecond(); + } + updated = reported_time != _last_time; _last_time = reported_time; _last_date = reported_date; - // FIXME TD-er: Must the offset in centisecond be added when pps_sync active? - if (!pps_sync) { - // Don't use the "commit" time when the sentence was read, but use the timestamp when the first sentence of a NMEA sequence was - // received. - const long time_since_start_seq = timePassedSince(_start_sequence); - - if (time_since_start_seq > P082_TIMESTAMP_AGE) { - return false; - } - age = time_since_start_seq; - - // FIXME TD-er: Are centiseconds expressed as 0.01 sec, or is it some fraction? - const uint8_t centiseconds = gps->time.centisecond(); - - if (centiseconds < 100) { - age += (gps->time.centisecond() * 10); - } - } - return true; } @@ -378,33 +466,74 @@ bool P082_data_struct::getDateTime(struct tm& dateTime) const bool P082_data_struct::tryUpdateSystemTime() { struct tm dateTime; + uint8_t centiseconds{}; uint32_t age{}; bool updated{}; - bool pps_sync{}; - if (getDateTime(dateTime, age, updated, pps_sync)) { + if (getDateTime(dateTime, centiseconds, age, updated)) { if (updated) { - // Use floating point precision to use the time since last update from GPS - // and the given offset in centisecond. - const uint32_t unixTime_sec = makeTime(dateTime) + (age / 1000); + const uint64_t cur_micros = getMicros64(); + + uint32_t second_frac_in_usec{}; + + if (validGpio(_ppsPin) && (_pps_time_micros > 0ll)) { + // Rely on timestamp from PPS pin, + // even when this might have been updated quite long ago + second_frac_in_usec = _pps_time_micros % 1000000ll; + } else if (_softwarePPS.getPPS(second_frac_in_usec)) { + // we got some estimate based on the timestamp of receiving the first sentence. + } else { + // Determine the system micros at the time the GPS time was committed + // This is the least accurate method. + const uint64_t micros = cur_micros - (age * 1000ull); + second_frac_in_usec = micros % 1000000ull; + } + + // First round to seconds + uint64_t sys_micros_at_start_second = cur_micros / 1000000ull; + sys_micros_at_start_second *= 1000000ull; + + // Add fraction of seconds (phase offset in usec) + sys_micros_at_start_second += second_frac_in_usec; + + if (sys_micros_at_start_second > cur_micros) { + sys_micros_at_start_second -= 1000000ull; + } + const uint64_t unixTime_usec = makeTime(dateTime) * 1000000ull + (10000ull * centiseconds); const uint64_t uptime_offset_usec = - sec_time_frac_to_uptime_offset_usec( - unixTime_sec, - millis_to_unix_time_frac(age % 1000)); + (unixTime_usec < sys_micros_at_start_second) + ? unixTime_usec + : (unixTime_usec - sys_micros_at_start_second); + _oversampling_gps_time_offset_usec.add(uptime_offset_usec); // Compute average over offset between system micros and GPS reported timestamp. // Both extremes will be filtered out - if (_oversampling_gps_time_offset_usec.getCount() == 5) { + if (_oversampling_gps_time_offset_usec.getCount() >= 5) { uint64_t value_usec{}; if (_oversampling_gps_time_offset_usec.get(value_usec)) { const double time = (getMicros64() + value_usec) / 1000000.0; - if (node_time.setExternalTimeSource(time, timeSource_t::GPS_time_source)) { + // Seed oversampling with the current average value + _oversampling_gps_time_offset_usec.add(value_usec); + + timeSource_t timeSource = timeSource_t::GPS_time_source_no_fix; + + if (hasFix(P082_TIMESTAMP_AGE)) { + // Using PPS sync should be extremely stable without any significant time wander. + // When we only can rely on keeping track of the timestamp at the start of a sentence, the fluctuation is significant. + if (usecPassedSince(_pps_time_micros) < 1000000ll) { + timeSource = timeSource_t::GPS_PPS_time_source; + } else { + timeSource = timeSource_t::GPS_time_source; + } + } + + if (node_time.setExternalTimeSource( + time, + timeSource)) { return true; - } else { - _oversampling_gps_time_offset_usec.add(value_usec); } } } @@ -517,6 +646,11 @@ bool P082_data_struct::writeToGPS(const uint8_t *data, size_t size) { return false; } +void ICACHE_RAM_ATTR P082_data_struct::pps_interrupt(P082_data_struct *self) +{ + self->_pps_time_micros = getMicros64(); +} + # if FEATURE_PLUGIN_STATS bool P082_data_struct::webformLoad_show_stats(struct EventStruct *event, uint8_t var_index, P082_query query_type) const { @@ -636,4 +770,13 @@ void P082_data_struct::webformLoad_show_position_scatterplot(struct EventStruct # endif // if FEATURE_CHART_JS # endif // if FEATURE_PLUGIN_STATS + +#ifndef BUILD_NO_DEBUG +String P082_data_struct::getPPSStats() const +{ + return _softwarePPS.getStats(); +} +#endif + + #endif // ifdef USES_P082 diff --git a/src/src/PluginStructs/P082_data_struct.h b/src/src/PluginStructs/P082_data_struct.h index 0c2a7d7fe9..d0858fe840 100644 --- a/src/src/PluginStructs/P082_data_struct.h +++ b/src/src/PluginStructs/P082_data_struct.h @@ -6,12 +6,13 @@ # include # include -# include "../Helpers/OversamplingHelper.h" +# include "../Helpers/ModuloOversamplingHelper.h" # ifndef BUILD_NO_DEBUG -# define P082_SEND_GPS_TO_LOG -//# define P082_USE_U_BLOX_SPECIFIC // TD-er: Disabled for now, as it is not working reliable/predictable -#endif +# define P082_SEND_GPS_TO_LOG + +// # define P082_USE_U_BLOX_SPECIFIC // TD-er: Disabled for now, as it is not working reliable/predictable +# endif // ifndef BUILD_NO_DEBUG # define P082_TIMESTAMP_AGE 1000 # define P082_DEFAULT_FIX_TIMEOUT 2500 // TTL of fix status in ms since last update @@ -63,15 +64,16 @@ enum class P082_query : uint8_t { P082_NR_OUTPUT_OPTIONS }; -const __FlashStringHelper * Plugin_082_valuename(P082_query value_nr, bool displayString); +const __FlashStringHelper* Plugin_082_valuename(P082_query value_nr, + bool displayString); -P082_query Plugin_082_from_valuename(const String& valuename); +P082_query Plugin_082_from_valuename(const String& valuename); enum class P082_PowerMode : uint8_t { Max_Performance = 0, - Power_Save = 1, - Eco = 2 + Power_Save = 1, + Eco = 2 }; const __FlashStringHelper* toString(P082_PowerMode mode); @@ -86,26 +88,65 @@ enum class P082_DynamicModel : uint8_t { Airborne_1g = 6, // airborne with <1g acceleration Airborne_2g = 7, // airborne with <2g acceleration Airborne_4g = 8, // airborne with <4g acceleration - Wrist = 9, // Only recommended for wrist-worn applications. Receiver will filter out armmotion (just available for protocol version > 17). - Bike = 10 // Used for applications with equivalent dynamics to those of a motor bike. Lowvertical acceleration assumed. (supported in protocol versions 19.2) + Wrist = 9, // Only recommended for wrist-worn applications. Receiver will filter out armmotion (just available for protocol version + // > 17). + Bike = 10 // Used for applications with equivalent dynamics to those of a motor bike. Lowvertical acceleration assumed. (supported + // in protocol versions 19.2) }; const __FlashStringHelper* toString(P082_DynamicModel model); -struct P082_data_struct : public PluginTaskData_base { +// Class to help determine the most likely fraction of a second +// when the burst of messages starts +// This fraction is relative to the system micros. +struct P082_software_pps { + P082_software_pps(); + + // Keep track of fraction of second relative to the system micros + // when the start of the sentence ('$') was received. + void addStartOfSentence(uint32_t bytesAvailableInSerialBuffer); + + // Commit the last recorded start of sentence time to the + // matching ModuloOversamplingHelper + void setSentenceType(TinyGPSPlus::GPS_Sentence_type sentenceType, uint32_t bytesAvailableInSerialBuffer); + + void setBaudrate(uint32_t baudrate); + + // Search for the second fraction of the sentence + // starting the burst of sentences, marking the start of a second. + bool getPPS(uint32_t& second_frac_in_usec) const; + +#ifndef BUILD_NO_DEBUG + String getStats() const; +#endif + +private: + + uint64_t bytesToUsec(uint32_t bytes) const; + + ModuloOversamplingHelper_second_frac_in_usec[TinyGPSPlus::GPS_SENTENCE_OTHER]{}; + + uint64_t _cur_start_sentence_usec = 0; + + int32_t _baudrate = 0; +}; + + +struct P082_data_struct : public PluginTaskData_base { // Enum is being stored, so don't change int values - + P082_data_struct(); virtual ~P082_data_struct(); -// void reset(); + // void reset(); bool init(ESPEasySerialPort port, const int16_t serial_rx, - const int16_t serial_tx); + const int16_t serial_tx, + const int8_t pps_pin); bool isInitialized() const { return gps != nullptr && easySerial != nullptr; @@ -122,18 +163,20 @@ struct P082_data_struct : public PluginTaskData_base { ESPEASY_RULES_FLOAT_TYPE distanceSinceLast(unsigned int maxAge_msec); private: + // Return the GPS time stamp, which is in UTC. // @param age is the time in msec since the last update of the time + // additional centiseconds given by the GPS. bool getDateTime(struct tm& dateTime, + uint8_t & centiseconds, uint32_t & age, - bool & updated, - bool & pps_sync); + bool & updated); + public: bool getDateTime(struct tm& dateTime) const; - // Try to fetch 5 timestamps in a row, filter out the peaks and use the average to set the + // Try to fetch 5 timestamps in a row, filter out the peaks and use the average to set the bool tryUpdateSystemTime(); // Send command to GPS to put it in PMREQ backup mode (UBLOX only) @@ -142,34 +185,51 @@ struct P082_data_struct : public PluginTaskData_base { // Send some characters to GPS to wake up bool wakeUp(); -#ifdef P082_USE_U_BLOX_SPECIFIC +# ifdef P082_USE_U_BLOX_SPECIFIC bool setPowerMode(P082_PowerMode mode); bool setDynamicModel(P082_DynamicModel model); -#endif +# endif // ifdef P082_USE_U_BLOX_SPECIFIC # if FEATURE_PLUGIN_STATS - bool webformLoad_show_stats(struct EventStruct *event, uint8_t var_index, P082_query query_type) const; + bool webformLoad_show_stats(struct EventStruct *event, + uint8_t var_index, + P082_query query_type) const; -#if FEATURE_CHART_JS +# if FEATURE_CHART_JS void webformLoad_show_position_scatterplot(struct EventStruct *event); +# endif // if FEATURE_CHART_JS +# endif // if FEATURE_PLUGIN_STATS + +#ifndef BUILD_NO_DEBUG + String getPPSStats() const; #endif -# endif // if FEATURE_PLUGIN_STATS + private: -#ifdef P082_USE_U_BLOX_SPECIFIC + +# ifdef P082_USE_U_BLOX_SPECIFIC + // Compute checksum // Caller should offset the data pointer to the correct start where the CRC should start. // @param size The length over which the CRC should be computed // @param CK_A, CK_B The 2 checksum bytes. - static void computeUbloxChecksum(const uint8_t* data, size_t size, uint8_t & CK_A, uint8_t & CK_B); + static void computeUbloxChecksum(const uint8_t *data, + size_t size, + uint8_t & CK_A, + uint8_t & CK_B); // Set checksum. // First 2 bytes of the array are skipped - static void setUbloxChecksum(uint8_t* data, size_t size); -#endif + static void setUbloxChecksum(uint8_t *data, + size_t size); +# endif // ifdef P082_USE_U_BLOX_SPECIFIC + + bool writeToGPS(const uint8_t *data, + size_t size); + + static void pps_interrupt(P082_data_struct *self); - bool writeToGPS(const uint8_t* data, size_t size); public: TinyGPSPlus *gps = nullptr; @@ -182,13 +242,13 @@ struct P082_data_struct : public PluginTaskData_base { ESPEASY_RULES_FLOAT_TYPE _distance{}; - unsigned long _pps_time = 0; - unsigned long _last_measurement = 0; - uint32_t _last_time = 0; - uint32_t _last_date = 0; - uint32_t _start_sentence = 0; - uint32_t _start_prev_sentence = 0; - uint32_t _start_sequence = 0; + unsigned long _last_measurement = 0; + uint32_t _last_time = 0; + uint32_t _last_date = 0; + + // uint32_t _start_sentence = 0; + // uint32_t _start_prev_sentence = 0; + // uint32_t _start_sequence = 0; # ifdef P082_SEND_GPS_TO_LOG String _lastSentence; String _currentSentence; @@ -196,7 +256,17 @@ struct P082_data_struct : public PluginTaskData_base { float _cache[static_cast(P082_query::P082_NR_OUTPUT_OPTIONS)]{}; - OversamplingHelper _oversampling_gps_time_offset_usec; + OversamplingHelper_oversampling_gps_time_offset_usec; + + P082_software_pps _softwarePPS; + + // When using PPS pin, we're only interested in the moment during a second when it triggers. + // So we keep only track of the micros() % 1000000 so we have some offset from the system micros counter. + // This will also be used to keep track of when the first sentence is received as the GPS will send those out in a burst at the start of a + // new second. + ESPEASY_VOLATILE(int64_t) _pps_time_micros = -1; + + int8_t _ppsPin = -1; }; #endif // ifdef USES_P082