├── .gitignore ├── README.md ├── examples ├── BasicExample │ └── BasicExample.ino ├── DeviceExample │ └── DeviceExample.ino ├── FullExample │ └── FullExample.ino ├── KitchenSink │ └── KitchenSink.ino ├── SatElevTracker │ ├── SatElevTracker.ino │ └── sample_satellite_elevation_log.txt ├── SatelliteTracker │ └── SatelliteTracker.ino └── UsingCustomFields │ └── UsingCustomFields.ino ├── keywords.txt ├── library.json ├── library.properties └── src ├── TinyGPS++.cpp ├── TinyGPS++.h └── TinyGPSPlus.h /.gitignore: -------------------------------------------------------------------------------- 1 | ################################################################################ 2 | # This .gitignore file was automatically created by Microsoft(R) Visual Studio. 3 | ################################################################################ 4 | 5 | /.vs 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TinyGPSPlus 2 | 3 | A new, customizable Arduino NMEA parsing library 4 | A *NEW* Full-featured GPS/NMEA Parser for Arduino 5 | TinyGPSPlus is a new Arduino library for parsing NMEA data streams provided by GPS modules. 6 | 7 | 1.1-beta update: Several pull requests incorporated (or equiv) 8 | 9 | * [38](https://github.com/mikalhart/TinyGPSPlus/pull/38) Added Fix Quality and Fix Mode 10 | * [66/109](https://github.com/mikalhart/TinyGPSPlus/pull/66) Fix stringop truncation warning 11 | * [69](https://github.com/mikalhart/TinyGPSPlus/pull/69) Support for non-Arduino platforms 12 | * [88](https://github.com/mikalhart/TinyGPSPlus/pull/88) Slight change to earth radius 13 | * [106](https://github.com/mikalhart/TinyGPSPlus/pull/106) Support all satellite groups 14 | 15 | Like its predecessor, TinyGPS, this library provides compact and easy-to-use methods for extracting position, date, time, altitude, speed, and course from consumer GPS devices. 16 | 17 | However, TinyGPSPlus’s programmer interface is considerably simpler to use than TinyGPS, and the new library can extract arbitrary data from any of the myriad NMEA sentences out there, even proprietary ones. 18 | 19 | See [Arduiniana - TinyGPSPlus](http://arduiniana.org/libraries/tinygpsplus/) for more detailed information on how to use TinyGPSPlus. 20 | -------------------------------------------------------------------------------- /examples/BasicExample/BasicExample.ino: -------------------------------------------------------------------------------- 1 | #include 2 | /* 3 | This sample sketch should be the first you try out when you are testing a TinyGPSPlus 4 | (TinyGPSPlus) installation. In normal use, you feed TinyGPSPlus objects characters from 5 | a serial NMEA GPS device, but this example uses static strings for simplicity. 6 | */ 7 | 8 | // A sample NMEA stream. 9 | const char *gpsStream = 10 | "$GPRMC,045103.000,A,3014.1984,N,09749.2872,W,0.67,161.46,030913,,,A*7C\r\n" 11 | "$GPGGA,045104.000,3014.1985,N,09749.2873,W,1,09,1.2,211.6,M,-22.5,M,,0000*62\r\n" 12 | "$GPRMC,045200.000,A,3014.3820,N,09748.9514,W,36.88,65.02,030913,,,A*77\r\n" 13 | "$GPGGA,045201.000,3014.3864,N,09748.9411,W,1,10,1.2,200.8,M,-22.5,M,,0000*6C\r\n" 14 | "$GPRMC,045251.000,A,3014.4275,N,09749.0626,W,0.51,217.94,030913,,,A*7D\r\n" 15 | "$GPGGA,045252.000,3014.4273,N,09749.0628,W,1,09,1.3,206.9,M,-22.5,M,,0000*6F\r\n"; 16 | 17 | // The TinyGPSPlus object 18 | TinyGPSPlus gps; 19 | 20 | void setup() 21 | { 22 | Serial.begin(115200); 23 | 24 | Serial.println(F("BasicExample.ino")); 25 | Serial.println(F("Basic demonstration of TinyGPSPlus (no device needed)")); 26 | Serial.print(F("Testing TinyGPSPlus library v. ")); Serial.println(TinyGPSPlus::libraryVersion()); 27 | Serial.println(F("by Mikal Hart")); 28 | Serial.println(); 29 | 30 | while (*gpsStream) 31 | if (gps.encode(*gpsStream++)) 32 | displayInfo(); 33 | 34 | Serial.println(); 35 | Serial.println(F("Done.")); 36 | } 37 | 38 | void loop() 39 | { 40 | } 41 | 42 | void displayInfo() 43 | { 44 | Serial.print(F("Location: ")); 45 | if (gps.location.isValid()) 46 | { 47 | Serial.print(gps.location.lat(), 6); 48 | Serial.print(F(",")); 49 | Serial.print(gps.location.lng(), 6); 50 | } 51 | else 52 | { 53 | Serial.print(F("INVALID")); 54 | } 55 | 56 | Serial.print(F(" Date/Time: ")); 57 | if (gps.date.isValid()) 58 | { 59 | Serial.print(gps.date.month()); 60 | Serial.print(F("/")); 61 | Serial.print(gps.date.day()); 62 | Serial.print(F("/")); 63 | Serial.print(gps.date.year()); 64 | } 65 | else 66 | { 67 | Serial.print(F("INVALID")); 68 | } 69 | 70 | Serial.print(F(" ")); 71 | if (gps.time.isValid()) 72 | { 73 | if (gps.time.hour() < 10) Serial.print(F("0")); 74 | Serial.print(gps.time.hour()); 75 | Serial.print(F(":")); 76 | if (gps.time.minute() < 10) Serial.print(F("0")); 77 | Serial.print(gps.time.minute()); 78 | Serial.print(F(":")); 79 | if (gps.time.second() < 10) Serial.print(F("0")); 80 | Serial.print(gps.time.second()); 81 | Serial.print(F(".")); 82 | if (gps.time.centisecond() < 10) Serial.print(F("0")); 83 | Serial.print(gps.time.centisecond()); 84 | } 85 | else 86 | { 87 | Serial.print(F("INVALID")); 88 | } 89 | 90 | Serial.println(); 91 | } 92 | -------------------------------------------------------------------------------- /examples/DeviceExample/DeviceExample.ino: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | /* 4 | This sample sketch demonstrates the normal use of a TinyGPSPlus (TinyGPSPlus) object. 5 | It requires the use of SoftwareSerial, and assumes that you have a 6 | 4800-baud serial GPS device hooked up on pins 4(rx) and 3(tx). 7 | */ 8 | static const int RXPin = 4, TXPin = 3; 9 | static const uint32_t GPSBaud = 4800; 10 | 11 | // The TinyGPSPlus object 12 | TinyGPSPlus gps; 13 | 14 | // The serial connection to the GPS device 15 | SoftwareSerial ss(RXPin, TXPin); 16 | 17 | void setup() 18 | { 19 | Serial.begin(115200); 20 | ss.begin(GPSBaud); 21 | 22 | Serial.println(F("DeviceExample.ino")); 23 | Serial.println(F("A simple demonstration of TinyGPSPlus with an attached GPS module")); 24 | Serial.print(F("Testing TinyGPSPlus library v. ")); Serial.println(TinyGPSPlus::libraryVersion()); 25 | Serial.println(F("by Mikal Hart")); 26 | Serial.println(); 27 | } 28 | 29 | void loop() 30 | { 31 | // This sketch displays information every time a new sentence is correctly encoded. 32 | while (ss.available() > 0) 33 | if (gps.encode(ss.read())) 34 | displayInfo(); 35 | 36 | if (millis() > 5000 && gps.charsProcessed() < 10) 37 | { 38 | Serial.println(F("No GPS detected: check wiring.")); 39 | while(true); 40 | } 41 | } 42 | 43 | void displayInfo() 44 | { 45 | Serial.print(F("Location: ")); 46 | if (gps.location.isValid()) 47 | { 48 | Serial.print(gps.location.lat(), 6); 49 | Serial.print(F(",")); 50 | Serial.print(gps.location.lng(), 6); 51 | } 52 | else 53 | { 54 | Serial.print(F("INVALID")); 55 | } 56 | 57 | Serial.print(F(" Date/Time: ")); 58 | if (gps.date.isValid()) 59 | { 60 | Serial.print(gps.date.month()); 61 | Serial.print(F("/")); 62 | Serial.print(gps.date.day()); 63 | Serial.print(F("/")); 64 | Serial.print(gps.date.year()); 65 | } 66 | else 67 | { 68 | Serial.print(F("INVALID")); 69 | } 70 | 71 | Serial.print(F(" ")); 72 | if (gps.time.isValid()) 73 | { 74 | if (gps.time.hour() < 10) Serial.print(F("0")); 75 | Serial.print(gps.time.hour()); 76 | Serial.print(F(":")); 77 | if (gps.time.minute() < 10) Serial.print(F("0")); 78 | Serial.print(gps.time.minute()); 79 | Serial.print(F(":")); 80 | if (gps.time.second() < 10) Serial.print(F("0")); 81 | Serial.print(gps.time.second()); 82 | Serial.print(F(".")); 83 | if (gps.time.centisecond() < 10) Serial.print(F("0")); 84 | Serial.print(gps.time.centisecond()); 85 | } 86 | else 87 | { 88 | Serial.print(F("INVALID")); 89 | } 90 | 91 | Serial.println(); 92 | } 93 | -------------------------------------------------------------------------------- /examples/FullExample/FullExample.ino: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | /* 4 | This sample code demonstrates the normal use of a TinyGPSPlus (TinyGPSPlus) object. 5 | It requires the use of SoftwareSerial, and assumes that you have a 6 | 4800-baud serial GPS device hooked up on pins 4(rx) and 3(tx). 7 | */ 8 | static const int RXPin = 4, TXPin = 3; 9 | static const uint32_t GPSBaud = 4800; 10 | 11 | // The TinyGPSPlus object 12 | TinyGPSPlus gps; 13 | 14 | // The serial connection to the GPS device 15 | SoftwareSerial ss(RXPin, TXPin); 16 | 17 | void setup() 18 | { 19 | Serial.begin(115200); 20 | ss.begin(GPSBaud); 21 | 22 | Serial.println(F("FullExample.ino")); 23 | Serial.println(F("An extensive example of many interesting TinyGPSPlus features")); 24 | Serial.print(F("Testing TinyGPSPlus library v. ")); Serial.println(TinyGPSPlus::libraryVersion()); 25 | Serial.println(F("by Mikal Hart")); 26 | Serial.println(); 27 | Serial.println(F("Sats HDOP Latitude Longitude Fix Date Time Date Alt Course Speed Card Distance Course Card Chars Sentences Checksum")); 28 | Serial.println(F(" (deg) (deg) Age Age (m) --- from GPS ---- ---- to London ---- RX RX Fail")); 29 | Serial.println(F("----------------------------------------------------------------------------------------------------------------------------------------")); 30 | } 31 | 32 | void loop() 33 | { 34 | static const double LONDON_LAT = 51.508131, LONDON_LON = -0.128002; 35 | 36 | printInt(gps.satellites.value(), gps.satellites.isValid(), 5); 37 | printFloat(gps.hdop.hdop(), gps.hdop.isValid(), 6, 1); 38 | printFloat(gps.location.lat(), gps.location.isValid(), 11, 6); 39 | printFloat(gps.location.lng(), gps.location.isValid(), 12, 6); 40 | printInt(gps.location.age(), gps.location.isValid(), 5); 41 | printDateTime(gps.date, gps.time); 42 | printFloat(gps.altitude.meters(), gps.altitude.isValid(), 7, 2); 43 | printFloat(gps.course.deg(), gps.course.isValid(), 7, 2); 44 | printFloat(gps.speed.kmph(), gps.speed.isValid(), 6, 2); 45 | printStr(gps.course.isValid() ? TinyGPSPlus::cardinal(gps.course.deg()) : "*** ", 6); 46 | 47 | unsigned long distanceKmToLondon = 48 | (unsigned long)TinyGPSPlus::distanceBetween( 49 | gps.location.lat(), 50 | gps.location.lng(), 51 | LONDON_LAT, 52 | LONDON_LON) / 1000; 53 | printInt(distanceKmToLondon, gps.location.isValid(), 9); 54 | 55 | double courseToLondon = 56 | TinyGPSPlus::courseTo( 57 | gps.location.lat(), 58 | gps.location.lng(), 59 | LONDON_LAT, 60 | LONDON_LON); 61 | 62 | printFloat(courseToLondon, gps.location.isValid(), 7, 2); 63 | 64 | const char *cardinalToLondon = TinyGPSPlus::cardinal(courseToLondon); 65 | 66 | printStr(gps.location.isValid() ? cardinalToLondon : "*** ", 6); 67 | 68 | printInt(gps.charsProcessed(), true, 6); 69 | printInt(gps.sentencesWithFix(), true, 10); 70 | printInt(gps.failedChecksum(), true, 9); 71 | Serial.println(); 72 | 73 | smartDelay(1000); 74 | 75 | if (millis() > 5000 && gps.charsProcessed() < 10) 76 | Serial.println(F("No GPS data received: check wiring")); 77 | } 78 | 79 | // This custom version of delay() ensures that the gps object 80 | // is being "fed". 81 | static void smartDelay(unsigned long ms) 82 | { 83 | unsigned long start = millis(); 84 | do 85 | { 86 | while (ss.available()) 87 | gps.encode(ss.read()); 88 | } while (millis() - start < ms); 89 | } 90 | 91 | static void printFloat(float val, bool valid, int len, int prec) 92 | { 93 | if (!valid) 94 | { 95 | while (len-- > 1) 96 | Serial.print('*'); 97 | Serial.print(' '); 98 | } 99 | else 100 | { 101 | Serial.print(val, prec); 102 | int vi = abs((int)val); 103 | int flen = prec + (val < 0.0 ? 2 : 1); // . and - 104 | flen += vi >= 1000 ? 4 : vi >= 100 ? 3 : vi >= 10 ? 2 : 1; 105 | for (int i=flen; i 0) 120 | sz[len-1] = ' '; 121 | Serial.print(sz); 122 | smartDelay(0); 123 | } 124 | 125 | static void printDateTime(TinyGPSDate &d, TinyGPSTime &t) 126 | { 127 | if (!d.isValid()) 128 | { 129 | Serial.print(F("********** ")); 130 | } 131 | else 132 | { 133 | char sz[32]; 134 | sprintf(sz, "%02d/%02d/%02d ", d.month(), d.day(), d.year()); 135 | Serial.print(sz); 136 | } 137 | 138 | if (!t.isValid()) 139 | { 140 | Serial.print(F("******** ")); 141 | } 142 | else 143 | { 144 | char sz[32]; 145 | sprintf(sz, "%02d:%02d:%02d ", t.hour(), t.minute(), t.second()); 146 | Serial.print(sz); 147 | } 148 | 149 | printInt(d.age(), d.isValid(), 5); 150 | smartDelay(0); 151 | } 152 | 153 | static void printStr(const char *str, int len) 154 | { 155 | int slen = strlen(str); 156 | for (int i=0; i 2 | #include 3 | /* 4 | This sample code demonstrates just about every built-in operation of TinyGPSPlus (TinyGPSPlus). 5 | It requires the use of SoftwareSerial, and assumes that you have a 6 | 4800-baud serial GPS device hooked up on pins 4(rx) and 3(tx). 7 | */ 8 | static const int RXPin = 4, TXPin = 3; 9 | static const uint32_t GPSBaud = 4800; 10 | 11 | // The TinyGPSPlus object 12 | TinyGPSPlus gps; 13 | 14 | // The serial connection to the GPS device 15 | SoftwareSerial ss(RXPin, TXPin); 16 | 17 | // For stats that happen every 5 seconds 18 | unsigned long last = 0UL; 19 | 20 | void setup() 21 | { 22 | Serial.begin(115200); 23 | ss.begin(GPSBaud); 24 | 25 | Serial.println(F("KitchenSink.ino")); 26 | Serial.println(F("Demonstrating nearly every feature of TinyGPSPlus")); 27 | Serial.print(F("Testing TinyGPSPlus library v. ")); Serial.println(TinyGPSPlus::libraryVersion()); 28 | Serial.println(F("by Mikal Hart")); 29 | Serial.println(); 30 | } 31 | 32 | void loop() 33 | { 34 | // Dispatch incoming characters 35 | while (ss.available() > 0) 36 | gps.encode(ss.read()); 37 | 38 | if (gps.location.isUpdated()) 39 | { 40 | Serial.print(F("LOCATION Fix Age=")); 41 | Serial.print(gps.location.age()); 42 | Serial.print(F("ms Raw Lat=")); 43 | Serial.print(gps.location.rawLat().negative ? "-" : "+"); 44 | Serial.print(gps.location.rawLat().deg); 45 | Serial.print("[+"); 46 | Serial.print(gps.location.rawLat().billionths); 47 | Serial.print(F(" billionths], Raw Long=")); 48 | Serial.print(gps.location.rawLng().negative ? "-" : "+"); 49 | Serial.print(gps.location.rawLng().deg); 50 | Serial.print("[+"); 51 | Serial.print(gps.location.rawLng().billionths); 52 | Serial.print(F(" billionths], Lat=")); 53 | Serial.print(gps.location.lat(), 6); 54 | Serial.print(F(" Long=")); 55 | Serial.println(gps.location.lng(), 6); 56 | } 57 | 58 | else if (gps.date.isUpdated()) 59 | { 60 | Serial.print(F("DATE Fix Age=")); 61 | Serial.print(gps.date.age()); 62 | Serial.print(F("ms Raw=")); 63 | Serial.print(gps.date.value()); 64 | Serial.print(F(" Year=")); 65 | Serial.print(gps.date.year()); 66 | Serial.print(F(" Month=")); 67 | Serial.print(gps.date.month()); 68 | Serial.print(F(" Day=")); 69 | Serial.println(gps.date.day()); 70 | } 71 | 72 | else if (gps.time.isUpdated()) 73 | { 74 | Serial.print(F("TIME Fix Age=")); 75 | Serial.print(gps.time.age()); 76 | Serial.print(F("ms Raw=")); 77 | Serial.print(gps.time.value()); 78 | Serial.print(F(" Hour=")); 79 | Serial.print(gps.time.hour()); 80 | Serial.print(F(" Minute=")); 81 | Serial.print(gps.time.minute()); 82 | Serial.print(F(" Second=")); 83 | Serial.print(gps.time.second()); 84 | Serial.print(F(" Hundredths=")); 85 | Serial.println(gps.time.centisecond()); 86 | } 87 | 88 | else if (gps.speed.isUpdated()) 89 | { 90 | Serial.print(F("SPEED Fix Age=")); 91 | Serial.print(gps.speed.age()); 92 | Serial.print(F("ms Raw=")); 93 | Serial.print(gps.speed.value()); 94 | Serial.print(F(" Knots=")); 95 | Serial.print(gps.speed.knots()); 96 | Serial.print(F(" MPH=")); 97 | Serial.print(gps.speed.mph()); 98 | Serial.print(F(" m/s=")); 99 | Serial.print(gps.speed.mps()); 100 | Serial.print(F(" km/h=")); 101 | Serial.println(gps.speed.kmph()); 102 | } 103 | 104 | else if (gps.course.isUpdated()) 105 | { 106 | Serial.print(F("COURSE Fix Age=")); 107 | Serial.print(gps.course.age()); 108 | Serial.print(F("ms Raw=")); 109 | Serial.print(gps.course.value()); 110 | Serial.print(F(" Deg=")); 111 | Serial.println(gps.course.deg()); 112 | } 113 | 114 | else if (gps.altitude.isUpdated()) 115 | { 116 | Serial.print(F("ALTITUDE Fix Age=")); 117 | Serial.print(gps.altitude.age()); 118 | Serial.print(F("ms Raw=")); 119 | Serial.print(gps.altitude.value()); 120 | Serial.print(F(" Meters=")); 121 | Serial.print(gps.altitude.meters()); 122 | Serial.print(F(" Miles=")); 123 | Serial.print(gps.altitude.miles()); 124 | Serial.print(F(" KM=")); 125 | Serial.print(gps.altitude.kilometers()); 126 | Serial.print(F(" Feet=")); 127 | Serial.println(gps.altitude.feet()); 128 | } 129 | 130 | else if (gps.satellites.isUpdated()) 131 | { 132 | Serial.print(F("SATELLITES Fix Age=")); 133 | Serial.print(gps.satellites.age()); 134 | Serial.print(F("ms Value=")); 135 | Serial.println(gps.satellites.value()); 136 | } 137 | 138 | else if (gps.hdop.isUpdated()) 139 | { 140 | Serial.print(F("HDOP Fix Age=")); 141 | Serial.print(gps.hdop.age()); 142 | Serial.print(F("ms raw=")); 143 | Serial.print(gps.hdop.value()); 144 | Serial.print(F(" hdop=")); 145 | Serial.println(gps.hdop.hdop()); 146 | } 147 | 148 | else if (millis() - last > 5000) 149 | { 150 | Serial.println(); 151 | if (gps.location.isValid()) 152 | { 153 | static const double LONDON_LAT = 51.508131, LONDON_LON = -0.128002; 154 | double distanceToLondon = 155 | TinyGPSPlus::distanceBetween( 156 | gps.location.lat(), 157 | gps.location.lng(), 158 | LONDON_LAT, 159 | LONDON_LON); 160 | double courseToLondon = 161 | TinyGPSPlus::courseTo( 162 | gps.location.lat(), 163 | gps.location.lng(), 164 | LONDON_LAT, 165 | LONDON_LON); 166 | 167 | Serial.print(F("LONDON Distance=")); 168 | Serial.print(distanceToLondon/1000, 6); 169 | Serial.print(F(" km Course-to=")); 170 | Serial.print(courseToLondon, 6); 171 | Serial.print(F(" degrees [")); 172 | Serial.print(TinyGPSPlus::cardinal(courseToLondon)); 173 | Serial.println(F("]")); 174 | } 175 | 176 | Serial.print(F("DIAGS Chars=")); 177 | Serial.print(gps.charsProcessed()); 178 | Serial.print(F(" Sentences-with-Fix=")); 179 | Serial.print(gps.sentencesWithFix()); 180 | Serial.print(F(" Failed-checksum=")); 181 | Serial.print(gps.failedChecksum()); 182 | Serial.print(F(" Passed-checksum=")); 183 | Serial.println(gps.passedChecksum()); 184 | 185 | if (gps.charsProcessed() < 10) 186 | Serial.println(F("WARNING: No GPS data. Check wiring.")); 187 | 188 | last = millis(); 189 | Serial.println(); 190 | } 191 | } -------------------------------------------------------------------------------- /examples/SatElevTracker/SatElevTracker.ino: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | /* 4 | This sample code tracks satellite elevations using TinyGPSCustom objects. 5 | 6 | Satellite numbers and elevations are not normally tracked by TinyGPSPlus, but 7 | by using TinyGPSCustom we get around this. 8 | 9 | It requires the use of SoftwareSerial and assumes that you have a 10 | 4800-baud serial GPS device hooked up on pins 4(RX) and 3(TX). 11 | */ 12 | static const int RXPin = 4, TXPin = 3; 13 | static const uint32_t GPSBaud = 4800; 14 | static const int MAX_SATELLITES = 40; 15 | static const int PAGE_LENGTH = 40; 16 | 17 | // The TinyGPSPlus object 18 | TinyGPSPlus gps; 19 | 20 | // The serial connection to the GPS device 21 | SoftwareSerial ss(RXPin, TXPin); 22 | 23 | TinyGPSCustom totalGPGSVMessages(gps, "GPGSV", 1); // $GPGSV sentence, first element 24 | TinyGPSCustom messageNumber(gps, "GPGSV", 2); // $GPGSV sentence, second element 25 | TinyGPSCustom satNumber[4]; // to be initialized later 26 | TinyGPSCustom elevation[4]; 27 | bool anyChanges = false; 28 | unsigned long linecount = 0; 29 | 30 | struct 31 | { 32 | int elevation; 33 | bool active; 34 | } sats[MAX_SATELLITES]; 35 | 36 | void setup() 37 | { 38 | Serial.begin(115200); 39 | ss.begin(GPSBaud); 40 | 41 | Serial.println(F("SatElevTracker.ino")); 42 | Serial.println(F("Displays GPS satellite elevations as they change")); 43 | Serial.print(F("Testing TinyGPSPlus library v. ")); Serial.println(TinyGPSPlus::libraryVersion()); 44 | Serial.println(F("by Mikal Hart")); 45 | Serial.println(); 46 | 47 | // Initialize all the uninitialized TinyGPSCustom objects 48 | for (int i=0; i<4; ++i) 49 | { 50 | satNumber[i].begin(gps, "GPGSV", 4 + 4 * i); // offsets 4, 8, 12, 16 51 | elevation[i].begin(gps, "GPGSV", 5 + 4 * i); // offsets 5, 9, 13, 17 52 | } 53 | } 54 | 55 | void loop() 56 | { 57 | // Dispatch incoming characters 58 | if (ss.available() > 0) 59 | { 60 | gps.encode(ss.read()); 61 | 62 | if (totalGPGSVMessages.isUpdated()) 63 | { 64 | for (int i=0; i<4; ++i) 65 | { 66 | int no = atoi(satNumber[i].value()); 67 | if (no >= 1 && no <= MAX_SATELLITES) 68 | { 69 | int elev = atoi(elevation[i].value()); 70 | sats[no-1].active = true; 71 | if (sats[no-1].elevation != elev) 72 | { 73 | sats[no-1].elevation = elev; 74 | anyChanges = true; 75 | } 76 | } 77 | } 78 | 79 | int totalMessages = atoi(totalGPGSVMessages.value()); 80 | int currentMessage = atoi(messageNumber.value()); 81 | if (totalMessages == currentMessage && anyChanges) 82 | { 83 | if (linecount++ % PAGE_LENGTH == 0) 84 | printHeader(); 85 | TimePrint(); 86 | for (int i=0; i 2 | #include 3 | /* 4 | This sample code demonstrates how to use an array of TinyGPSCustom objects 5 | to monitor all the visible satellites. 6 | 7 | Satellite numbers, elevation, azimuth, and signal-to-noise ratio are not 8 | normally tracked by TinyGPSPlus, but by using TinyGPSCustom we get around this. 9 | 10 | The simple code also demonstrates how to use arrays of TinyGPSCustom objects, 11 | each monitoring a different field of the $GPGSV sentence. 12 | 13 | It requires the use of SoftwareSerial, and assumes that you have a 14 | 4800-baud serial GPS device hooked up on pins 4(RX) and 3(TX). 15 | */ 16 | static const int RXPin = 4, TXPin = 3; 17 | static const uint32_t GPSBaud = 4800; 18 | 19 | // The TinyGPSPlus object 20 | TinyGPSPlus gps; 21 | 22 | // The serial connection to the GPS device 23 | SoftwareSerial ss(RXPin, TXPin); 24 | 25 | /* 26 | From http://aprs.gids.nl/nmea/: 27 | 28 | $GPGSV 29 | 30 | GPS Satellites in view 31 | 32 | eg. $GPGSV,3,1,11,03,03,111,00,04,15,270,00,06,01,010,00,13,06,292,00*74 33 | $GPGSV,3,2,11,14,25,170,00,16,57,208,39,18,67,296,40,19,40,246,00*74 34 | $GPGSV,3,3,11,22,42,067,42,24,14,311,43,27,05,244,00,,,,*4D 35 | 36 | 1 = Total number of messages of this type in this cycle 37 | 2 = Message number 38 | 3 = Total number of SVs in view 39 | 4 = SV PRN number 40 | 5 = Elevation in degrees, 90 maximum 41 | 6 = Azimuth, degrees from true north, 000 to 359 42 | 7 = SNR, 00-99 dB (null when not tracking) 43 | 8-11 = Information about second SV, same as field 4-7 44 | 12-15= Information about third SV, same as field 4-7 45 | 16-19= Information about fourth SV, same as field 4-7 46 | */ 47 | 48 | static const int MAX_SATELLITES = 40; 49 | 50 | TinyGPSCustom totalGPGSVMessages(gps, "GPGSV", 1); // $GPGSV sentence, first element 51 | TinyGPSCustom messageNumber(gps, "GPGSV", 2); // $GPGSV sentence, second element 52 | TinyGPSCustom satsInView(gps, "GPGSV", 3); // $GPGSV sentence, third element 53 | TinyGPSCustom satNumber[4]; // to be initialized later 54 | TinyGPSCustom elevation[4]; 55 | TinyGPSCustom azimuth[4]; 56 | TinyGPSCustom snr[4]; 57 | 58 | struct 59 | { 60 | bool active; 61 | int elevation; 62 | int azimuth; 63 | int snr; 64 | } sats[MAX_SATELLITES]; 65 | 66 | void setup() 67 | { 68 | Serial.begin(115200); 69 | ss.begin(GPSBaud); 70 | 71 | Serial.println(F("SatelliteTracker.ino")); 72 | Serial.println(F("Monitoring satellite location and signal strength using TinyGPSCustom")); 73 | Serial.print(F("Testing TinyGPSPlus library v. ")); Serial.println(TinyGPSPlus::libraryVersion()); 74 | Serial.println(F("by Mikal Hart")); 75 | Serial.println(); 76 | 77 | // Initialize all the uninitialized TinyGPSCustom objects 78 | for (int i=0; i<4; ++i) 79 | { 80 | satNumber[i].begin(gps, "GPGSV", 4 + 4 * i); // offsets 4, 8, 12, 16 81 | elevation[i].begin(gps, "GPGSV", 5 + 4 * i); // offsets 5, 9, 13, 17 82 | azimuth[i].begin( gps, "GPGSV", 6 + 4 * i); // offsets 6, 10, 14, 18 83 | snr[i].begin( gps, "GPGSV", 7 + 4 * i); // offsets 7, 11, 15, 19 84 | } 85 | } 86 | 87 | void loop() 88 | { 89 | // Dispatch incoming characters 90 | if (ss.available() > 0) 91 | { 92 | gps.encode(ss.read()); 93 | if (totalGPGSVMessages.isUpdated()) 94 | { 95 | for (int i=0; i<4; ++i) 96 | { 97 | int no = atoi(satNumber[i].value()); 98 | // Serial.print(F("SatNumber is ")); Serial.println(no); 99 | if (no >= 1 && no <= MAX_SATELLITES) 100 | { 101 | sats[no-1].elevation = atoi(elevation[i].value()); 102 | sats[no-1].azimuth = atoi(azimuth[i].value()); 103 | sats[no-1].snr = atoi(snr[i].value()); 104 | sats[no-1].active = true; 105 | } 106 | } 107 | 108 | int totalMessages = atoi(totalGPGSVMessages.value()); 109 | int currentMessage = atoi(messageNumber.value()); 110 | if (totalMessages == currentMessage) 111 | { 112 | Serial.print(F("Sats=")); Serial.print(gps.satellites.value()); 113 | Serial.print(F(" Nums=")); 114 | for (int i=0; i 2 | #include 3 | 4 | /* 5 | This sample demonstrates TinyGPSPlus's capacity for extracting custom 6 | fields from any NMEA sentence. TinyGPSPlus has built-in facilities for 7 | extracting latitude, longitude, altitude, etc., from the $GPGGA and 8 | $GPRMC sentences. But with the TinyGPSCustom type, you can extract 9 | other NMEA fields, even from non-standard NMEA sentences. 10 | 11 | It requires the use of SoftwareSerial, and assumes that you have a 12 | 4800-baud serial GPS device hooked up on pins 4(RX) and 3(TX). 13 | */ 14 | static const int RXPin = 4, TXPin = 3; 15 | static const uint32_t GPSBaud = 4800; 16 | 17 | // The TinyGPSPlus object 18 | TinyGPSPlus gps; 19 | 20 | // The serial connection to the GPS device 21 | SoftwareSerial ss(RXPin, TXPin); 22 | 23 | /* 24 | By declaring TinyGPSCustom objects like this, we announce that we 25 | are interested in the 15th, 16th, and 17th fields in the $GPGSA 26 | sentence, respectively the PDOP (F("positional dilution of precision")), 27 | HDOP (F("horizontal...")), and VDOP (F("vertical...")). 28 | 29 | (Counting starts with the field immediately following the sentence name, 30 | i.e. $GPGSA. For more information on NMEA sentences, consult your 31 | GPS module's documentation and/or http://aprs.gids.nl/nmea/.) 32 | 33 | If your GPS module doesn't support the $GPGSA sentence, then you 34 | won't get any output from this program. 35 | */ 36 | 37 | TinyGPSCustom pdop(gps, "GPGSA", 15); // $GPGSA sentence, 15th element 38 | TinyGPSCustom hdop(gps, "GPGSA", 16); // $GPGSA sentence, 16th element 39 | TinyGPSCustom vdop(gps, "GPGSA", 17); // $GPGSA sentence, 17th element 40 | 41 | void setup() 42 | { 43 | Serial.begin(115200); 44 | ss.begin(GPSBaud); 45 | 46 | Serial.println(F("UsingCustomFields.ino")); 47 | Serial.println(F("Demonstrating how to extract any NMEA field using TinyGPSCustom")); 48 | Serial.print(F("Testing TinyGPSPlus library v. ")); Serial.println(TinyGPSPlus::libraryVersion()); 49 | Serial.println(F("by Mikal Hart")); 50 | Serial.println(); 51 | } 52 | 53 | void loop() 54 | { 55 | // Every time anything is updated, print everything. 56 | if (gps.altitude.isUpdated() || gps.satellites.isUpdated() || 57 | pdop.isUpdated() || hdop.isUpdated() || vdop.isUpdated()) 58 | { 59 | Serial.print(F("ALT=")); Serial.print(gps.altitude.meters()); 60 | Serial.print(F(" PDOP=")); Serial.print(pdop.value()); 61 | Serial.print(F(" HDOP=")); Serial.print(hdop.value()); 62 | Serial.print(F(" VDOP=")); Serial.print(vdop.value()); 63 | Serial.print(F(" SATS=")); Serial.println(gps.satellites.value()); 64 | } 65 | 66 | while (ss.available() > 0) 67 | gps.encode(ss.read()); 68 | } 69 | 70 | -------------------------------------------------------------------------------- /keywords.txt: -------------------------------------------------------------------------------- 1 | ####################################### 2 | # Syntax Coloring Map for TinyGPSPlus 3 | ####################################### 4 | 5 | ####################################### 6 | # Datatypes (KEYWORD1) 7 | ####################################### 8 | 9 | TinyGPSPlus KEYWORD1 10 | TinyGPSLocation KEYWORD1 11 | TinyGPSDate KEYWORD1 12 | TinyGPSTime KEYWORD1 13 | TinyGPSSpeed KEYWORD1 14 | TinyGPSCourse KEYWORD1 15 | TinyGPSAltitude KEYWORD1 16 | TinyGPSInteger KEYWORD1 17 | TinyGPSDecimal KEYWORD1 18 | TinyGPSCustom KEYWORD1 19 | 20 | ####################################### 21 | # Methods and Functions (KEYWORD2) 22 | ####################################### 23 | 24 | encode KEYWORD2 25 | location KEYWORD2 26 | date KEYWORD2 27 | time KEYWORD2 28 | speed KEYWORD2 29 | course KEYWORD2 30 | altitude KEYWORD2 31 | satellites KEYWORD2 32 | hdop KEYWORD2 33 | libraryVersion KEYWORD2 34 | distanceBetween KEYWORD2 35 | courseTo KEYWORD2 36 | cardinal KEYWORD2 37 | charsProcessed KEYWORD2 38 | sentencesWithFix KEYWORD2 39 | failedChecksum KEYWORD2 40 | passedChecksum KEYWORD2 41 | isValid KEYWORD2 42 | isUpdated KEYWORD2 43 | age KEYWORD2 44 | lat KEYWORD2 45 | lng KEYWORD2 46 | isUpdatedDate KEYWORD2 47 | isUpdatedTime KEYWORD2 48 | year KEYWORD2 49 | month KEYWORD2 50 | day KEYWORD2 51 | hour KEYWORD2 52 | minute KEYWORD2 53 | second KEYWORD2 54 | centisecond KEYWORD2 55 | value KEYWORD2 56 | knots KEYWORD2 57 | mph KEYWORD2 58 | mps KEYWORD2 59 | kmph KEYWORD2 60 | deg KEYWORD2 61 | billionths KEYWORD2 62 | negative KEYWORD2 63 | meters KEYWORD2 64 | miles KEYWORD2 65 | kilometers KEYWORD2 66 | feet KEYWORD2 67 | 68 | ####################################### 69 | # Constants (LITERAL1) 70 | ####################################### 71 | 72 | -------------------------------------------------------------------------------- /library.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "TinyGPSPlus", 3 | "version": "1.1.0", 4 | "keywords": "gps,NMEA", 5 | "description": "A tiny, customizable Arduino NMEA parsing library", 6 | "repository": 7 | { 8 | "type": "git", 9 | "url": "https://github.com/mikalhart/TinyGPSPlus.git" 10 | }, 11 | "authors": 12 | [ 13 | { 14 | "name": "Mikal Hart", 15 | "email": "mikal@arduniana.org", 16 | "url": "http://arduiniana.org", 17 | "maintainer": true 18 | } 19 | ], 20 | "frameworks": "arduino", 21 | "platforms": "*" 22 | } 23 | -------------------------------------------------------------------------------- /library.properties: -------------------------------------------------------------------------------- 1 | name=TinyGPSPlus 2 | version=1.1.0 3 | author=Mikal Hart 4 | maintainer=Mikal Hart 5 | sentence=TinyGPSPlus provides object-oriented parsing of GPS (NMEA) sentences 6 | paragraph=NMEA is the standard format GPS devices use to report location, time, altitude, etc. TinyGPSPlus is a compact, resilient library that parses the most common NMEA 'sentences' used: GGA and RMC. It can also be customized to extract data from *any* compliant sentence. 7 | category=Communication 8 | url=https://github.com/mikalhart/TinyGPSPlus 9 | architectures=* 10 | -------------------------------------------------------------------------------- /src/TinyGPS++.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | TinyGPS++ - a small GPS library for Arduino providing universal NMEA parsing 3 | Based on work by and "distanceBetween" and "courseTo" courtesy of Maarten Lamers. 4 | Suggestion to add satellites, courseTo(), and cardinal() by Matt Monson. 5 | Location precision improvements suggested by Wayne Holder. 6 | Copyright (C) 2008-2024 Mikal Hart 7 | All rights reserved. 8 | 9 | This library is free software; you can redistribute it and/or 10 | modify it under the terms of the GNU Lesser General Public 11 | License as published by the Free Software Foundation; either 12 | version 2.1 of the License, or (at your option) any later version. 13 | 14 | This library is distributed in the hope that it will be useful, 15 | but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 17 | Lesser General Public License for more details. 18 | 19 | You should have received a copy of the GNU Lesser General Public 20 | License along with this library; if not, write to the Free Software 21 | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 22 | */ 23 | 24 | #include "TinyGPS++.h" 25 | 26 | #include 27 | #include 28 | #include 29 | 30 | #define _RMCterm "RMC" 31 | #define _GGAterm "GGA" 32 | 33 | #if !defined(ARDUINO) && !defined(__AVR__) 34 | // Alternate implementation of millis() that relies on std 35 | unsigned long millis() 36 | { 37 | static auto start_time = std::chrono::high_resolution_clock::now(); 38 | 39 | auto end_time = std::chrono::high_resolution_clock::now(); 40 | auto duration = std::chrono::duration_cast(end_time - start_time); 41 | 42 | return static_cast(duration.count()); 43 | } 44 | #endif 45 | 46 | TinyGPSPlus::TinyGPSPlus() 47 | : parity(0) 48 | , isChecksumTerm(false) 49 | , curSentenceType(GPS_SENTENCE_OTHER) 50 | , curTermNumber(0) 51 | , curTermOffset(0) 52 | , sentenceHasFix(false) 53 | , customElts(0) 54 | , customCandidates(0) 55 | , encodedCharCount(0) 56 | , sentencesWithFixCount(0) 57 | , failedChecksumCount(0) 58 | , passedChecksumCount(0) 59 | { 60 | term[0] = '\0'; 61 | } 62 | 63 | // 64 | // public methods 65 | // 66 | 67 | bool TinyGPSPlus::encode(char c) 68 | { 69 | ++encodedCharCount; 70 | 71 | switch(c) 72 | { 73 | case ',': // term terminators 74 | parity ^= (uint8_t)c; 75 | case '\r': 76 | case '\n': 77 | case '*': 78 | { 79 | bool isValidSentence = false; 80 | if (curTermOffset < sizeof(term)) 81 | { 82 | term[curTermOffset] = 0; 83 | isValidSentence = endOfTermHandler(); 84 | } 85 | ++curTermNumber; 86 | curTermOffset = 0; 87 | isChecksumTerm = c == '*'; 88 | return isValidSentence; 89 | } 90 | break; 91 | 92 | case '$': // sentence begin 93 | curTermNumber = curTermOffset = 0; 94 | parity = 0; 95 | curSentenceType = GPS_SENTENCE_OTHER; 96 | isChecksumTerm = false; 97 | sentenceHasFix = false; 98 | return false; 99 | 100 | default: // ordinary characters 101 | if (curTermOffset < sizeof(term) - 1) 102 | term[curTermOffset++] = c; 103 | if (!isChecksumTerm) 104 | parity ^= c; 105 | return false; 106 | } 107 | 108 | return false; 109 | } 110 | 111 | // 112 | // internal utilities 113 | // 114 | int TinyGPSPlus::fromHex(char a) 115 | { 116 | if (a >= 'A' && a <= 'F') 117 | return a - 'A' + 10; 118 | else if (a >= 'a' && a <= 'f') 119 | return a - 'a' + 10; 120 | else 121 | return a - '0'; 122 | } 123 | 124 | // static 125 | // Parse a (potentially negative) number with up to 2 decimal digits -xxxx.yy 126 | int32_t TinyGPSPlus::parseDecimal(const char *term) 127 | { 128 | bool negative = *term == '-'; 129 | if (negative) ++term; 130 | int32_t ret = 100 * (int32_t)atol(term); 131 | while (isdigit(*term)) ++term; 132 | if (*term == '.' && isdigit(term[1])) 133 | { 134 | ret += 10 * (term[1] - '0'); 135 | if (isdigit(term[2])) 136 | ret += term[2] - '0'; 137 | } 138 | return negative ? -ret : ret; 139 | } 140 | 141 | // static 142 | // Parse degrees in that funny NMEA format DDMM.MMMM 143 | void TinyGPSPlus::parseDegrees(const char *term, RawDegrees °) 144 | { 145 | uint32_t leftOfDecimal = (uint32_t)atol(term); 146 | uint16_t minutes = (uint16_t)(leftOfDecimal % 100); 147 | uint32_t multiplier = 10000000UL; 148 | uint32_t tenMillionthsOfMinutes = minutes * multiplier; 149 | 150 | deg.deg = (int16_t)(leftOfDecimal / 100); 151 | 152 | while (isdigit(*term)) 153 | ++term; 154 | 155 | if (*term == '.') 156 | while (isdigit(*++term)) 157 | { 158 | multiplier /= 10; 159 | tenMillionthsOfMinutes += (*term - '0') * multiplier; 160 | } 161 | 162 | deg.billionths = (5 * tenMillionthsOfMinutes + 1) / 3; 163 | deg.negative = false; 164 | } 165 | 166 | #define COMBINE(sentence_type, term_number) (((unsigned)(sentence_type) << 5) | term_number) 167 | 168 | // Processes a just-completed term 169 | // Returns true if new sentence has just passed checksum test and is validated 170 | bool TinyGPSPlus::endOfTermHandler() 171 | { 172 | // If it's the checksum term, and the checksum checks out, commit 173 | if (isChecksumTerm) 174 | { 175 | byte checksum = 16 * fromHex(term[0]) + fromHex(term[1]); 176 | if (checksum == parity) 177 | { 178 | passedChecksumCount++; 179 | if (sentenceHasFix) 180 | ++sentencesWithFixCount; 181 | 182 | switch(curSentenceType) 183 | { 184 | case GPS_SENTENCE_RMC: 185 | date.commit(); 186 | time.commit(); 187 | if (sentenceHasFix) 188 | { 189 | location.commit(); 190 | speed.commit(); 191 | course.commit(); 192 | } 193 | break; 194 | case GPS_SENTENCE_GGA: 195 | time.commit(); 196 | if (sentenceHasFix) 197 | { 198 | location.commit(); 199 | altitude.commit(); 200 | } 201 | satellites.commit(); 202 | hdop.commit(); 203 | break; 204 | } 205 | 206 | // Commit all custom listeners of this sentence type 207 | for (TinyGPSCustom *p = customCandidates; p != NULL && strcmp(p->sentenceName, customCandidates->sentenceName) == 0; p = p->next) 208 | p->commit(); 209 | return true; 210 | } 211 | 212 | else 213 | { 214 | ++failedChecksumCount; 215 | } 216 | 217 | return false; 218 | } 219 | 220 | // the first term determines the sentence type 221 | if (curTermNumber == 0) 222 | { 223 | if (term[0] == 'G' && strchr("PNABL", term[1]) != NULL && !strcmp(term + 2, _RMCterm)) 224 | curSentenceType = GPS_SENTENCE_RMC; 225 | else if (term[0] == 'G' && strchr("PNABL", term[1]) != NULL && !strcmp(term + 2, _GGAterm)) 226 | curSentenceType = GPS_SENTENCE_GGA; 227 | else 228 | curSentenceType = GPS_SENTENCE_OTHER; 229 | 230 | // Any custom candidates of this sentence type? 231 | for (customCandidates = customElts; customCandidates != NULL && strcmp(customCandidates->sentenceName, term) < 0; customCandidates = customCandidates->next); 232 | if (customCandidates != NULL && strcmp(customCandidates->sentenceName, term) > 0) 233 | customCandidates = NULL; 234 | 235 | return false; 236 | } 237 | 238 | if (curSentenceType != GPS_SENTENCE_OTHER && term[0]) 239 | switch(COMBINE(curSentenceType, curTermNumber)) 240 | { 241 | case COMBINE(GPS_SENTENCE_RMC, 1): // Time in both sentences 242 | case COMBINE(GPS_SENTENCE_GGA, 1): 243 | time.setTime(term); 244 | break; 245 | case COMBINE(GPS_SENTENCE_RMC, 2): // RMC validity 246 | sentenceHasFix = term[0] == 'A'; 247 | break; 248 | case COMBINE(GPS_SENTENCE_RMC, 3): // Latitude 249 | case COMBINE(GPS_SENTENCE_GGA, 2): 250 | location.setLatitude(term); 251 | break; 252 | case COMBINE(GPS_SENTENCE_RMC, 4): // N/S 253 | case COMBINE(GPS_SENTENCE_GGA, 3): 254 | location.rawNewLatData.negative = term[0] == 'S'; 255 | break; 256 | case COMBINE(GPS_SENTENCE_RMC, 5): // Longitude 257 | case COMBINE(GPS_SENTENCE_GGA, 4): 258 | location.setLongitude(term); 259 | break; 260 | case COMBINE(GPS_SENTENCE_RMC, 6): // E/W 261 | case COMBINE(GPS_SENTENCE_GGA, 5): 262 | location.rawNewLngData.negative = term[0] == 'W'; 263 | break; 264 | case COMBINE(GPS_SENTENCE_RMC, 7): // Speed (RMC) 265 | speed.set(term); 266 | break; 267 | case COMBINE(GPS_SENTENCE_RMC, 8): // Course (RMC) 268 | course.set(term); 269 | break; 270 | case COMBINE(GPS_SENTENCE_RMC, 9): // Date (RMC) 271 | date.setDate(term); 272 | break; 273 | case COMBINE(GPS_SENTENCE_GGA, 6): // Fix data (GGA) 274 | sentenceHasFix = term[0] > '0'; 275 | location.newFixQuality = (TinyGPSLocation::Quality)term[0]; 276 | break; 277 | case COMBINE(GPS_SENTENCE_GGA, 7): // Satellites used (GGA) 278 | satellites.set(term); 279 | break; 280 | case COMBINE(GPS_SENTENCE_GGA, 8): // HDOP 281 | hdop.set(term); 282 | break; 283 | case COMBINE(GPS_SENTENCE_GGA, 9): // Altitude (GGA) 284 | altitude.set(term); 285 | break; 286 | case COMBINE(GPS_SENTENCE_RMC, 12): 287 | location.newFixMode = (TinyGPSLocation::Mode)term[0]; 288 | break; 289 | } 290 | 291 | // Set custom values as needed 292 | for (TinyGPSCustom *p = customCandidates; p != NULL && strcmp(p->sentenceName, customCandidates->sentenceName) == 0 && p->termNumber <= curTermNumber; p = p->next) 293 | if (p->termNumber == curTermNumber) 294 | p->set(term); 295 | 296 | return false; 297 | } 298 | 299 | /* static */ 300 | double TinyGPSPlus::distanceBetween(double lat1, double long1, double lat2, double long2) 301 | { 302 | // returns distance in meters between two positions, both specified 303 | // as signed decimal-degrees latitude and longitude. Uses great-circle 304 | // distance computation for hypothetical sphere of radius 6371009 meters. 305 | // Because Earth is no exact sphere, rounding errors may be up to 0.5%. 306 | // Courtesy of Maarten Lamers 307 | double delta = radians(long1-long2); 308 | double sdlong = sin(delta); 309 | double cdlong = cos(delta); 310 | lat1 = radians(lat1); 311 | lat2 = radians(lat2); 312 | double slat1 = sin(lat1); 313 | double clat1 = cos(lat1); 314 | double slat2 = sin(lat2); 315 | double clat2 = cos(lat2); 316 | delta = (clat1 * slat2) - (slat1 * clat2 * cdlong); 317 | delta = sq(delta); 318 | delta += sq(clat2 * sdlong); 319 | delta = sqrt(delta); 320 | double denom = (slat1 * slat2) + (clat1 * clat2 * cdlong); 321 | delta = atan2(delta, denom); 322 | return delta * _GPS_EARTH_MEAN_RADIUS; 323 | } 324 | 325 | double TinyGPSPlus::courseTo(double lat1, double long1, double lat2, double long2) 326 | { 327 | // returns course in degrees (North=0, West=270) from position 1 to position 2, 328 | // both specified as signed decimal-degrees latitude and longitude. 329 | // Because Earth is no exact sphere, calculated course may be off by a tiny fraction. 330 | // Courtesy of Maarten Lamers 331 | double dlon = radians(long2-long1); 332 | lat1 = radians(lat1); 333 | lat2 = radians(lat2); 334 | double a1 = sin(dlon) * cos(lat2); 335 | double a2 = sin(lat1) * cos(lat2) * cos(dlon); 336 | a2 = cos(lat1) * sin(lat2) - a2; 337 | a2 = atan2(a1, a2); 338 | if (a2 < 0.0) 339 | { 340 | a2 += TWO_PI; 341 | } 342 | return degrees(a2); 343 | } 344 | 345 | const char *TinyGPSPlus::cardinal(double course) 346 | { 347 | static const char* directions[] = {"N", "NNE", "NE", "ENE", "E", "ESE", "SE", "SSE", "S", "SSW", "SW", "WSW", "W", "WNW", "NW", "NNW"}; 348 | int direction = (int)((course + 11.25f) / 22.5f); 349 | return directions[direction % 16]; 350 | } 351 | 352 | void TinyGPSLocation::commit() 353 | { 354 | rawLatData = rawNewLatData; 355 | rawLngData = rawNewLngData; 356 | fixQuality = newFixQuality; 357 | fixMode = newFixMode; 358 | lastCommitTime = millis(); 359 | valid = updated = true; 360 | } 361 | 362 | void TinyGPSLocation::setLatitude(const char *term) 363 | { 364 | TinyGPSPlus::parseDegrees(term, rawNewLatData); 365 | } 366 | 367 | void TinyGPSLocation::setLongitude(const char *term) 368 | { 369 | TinyGPSPlus::parseDegrees(term, rawNewLngData); 370 | } 371 | 372 | double TinyGPSLocation::lat() 373 | { 374 | updated = false; 375 | double ret = rawLatData.deg + rawLatData.billionths / 1000000000.0; 376 | return rawLatData.negative ? -ret : ret; 377 | } 378 | 379 | double TinyGPSLocation::lng() 380 | { 381 | updated = false; 382 | double ret = rawLngData.deg + rawLngData.billionths / 1000000000.0; 383 | return rawLngData.negative ? -ret : ret; 384 | } 385 | 386 | void TinyGPSDate::commit() 387 | { 388 | date = newDate; 389 | lastCommitTime = millis(); 390 | valid = updated = true; 391 | } 392 | 393 | void TinyGPSTime::commit() 394 | { 395 | time = newTime; 396 | lastCommitTime = millis(); 397 | valid = updated = true; 398 | } 399 | 400 | void TinyGPSTime::setTime(const char *term) 401 | { 402 | newTime = (uint32_t)TinyGPSPlus::parseDecimal(term); 403 | } 404 | 405 | void TinyGPSDate::setDate(const char *term) 406 | { 407 | newDate = atol(term); 408 | } 409 | 410 | uint16_t TinyGPSDate::year() 411 | { 412 | updated = false; 413 | uint16_t year = date % 100; 414 | return year + 2000; 415 | } 416 | 417 | uint8_t TinyGPSDate::month() 418 | { 419 | updated = false; 420 | return (date / 100) % 100; 421 | } 422 | 423 | uint8_t TinyGPSDate::day() 424 | { 425 | updated = false; 426 | return date / 10000; 427 | } 428 | 429 | uint8_t TinyGPSTime::hour() 430 | { 431 | updated = false; 432 | return time / 1000000; 433 | } 434 | 435 | uint8_t TinyGPSTime::minute() 436 | { 437 | updated = false; 438 | return (time / 10000) % 100; 439 | } 440 | 441 | uint8_t TinyGPSTime::second() 442 | { 443 | updated = false; 444 | return (time / 100) % 100; 445 | } 446 | 447 | uint8_t TinyGPSTime::centisecond() 448 | { 449 | updated = false; 450 | return time % 100; 451 | } 452 | 453 | void TinyGPSDecimal::commit() 454 | { 455 | val = newval; 456 | lastCommitTime = millis(); 457 | valid = updated = true; 458 | } 459 | 460 | void TinyGPSDecimal::set(const char *term) 461 | { 462 | newval = TinyGPSPlus::parseDecimal(term); 463 | } 464 | 465 | void TinyGPSInteger::commit() 466 | { 467 | val = newval; 468 | lastCommitTime = millis(); 469 | valid = updated = true; 470 | } 471 | 472 | void TinyGPSInteger::set(const char *term) 473 | { 474 | newval = atol(term); 475 | } 476 | 477 | TinyGPSCustom::TinyGPSCustom(TinyGPSPlus &gps, const char *_sentenceName, int _termNumber) 478 | { 479 | begin(gps, _sentenceName, _termNumber); 480 | } 481 | 482 | void TinyGPSCustom::begin(TinyGPSPlus &gps, const char *_sentenceName, int _termNumber) 483 | { 484 | lastCommitTime = 0; 485 | updated = valid = false; 486 | sentenceName = _sentenceName; 487 | termNumber = _termNumber; 488 | memset(stagingBuffer, '\0', sizeof(stagingBuffer)); 489 | memset(buffer, '\0', sizeof(buffer)); 490 | 491 | // Insert this item into the GPS tree 492 | gps.insertCustom(this, _sentenceName, _termNumber); 493 | } 494 | 495 | void TinyGPSCustom::commit() 496 | { 497 | strcpy(this->buffer, this->stagingBuffer); 498 | lastCommitTime = millis(); 499 | valid = updated = true; 500 | } 501 | 502 | void TinyGPSCustom::set(const char *term) 503 | { 504 | strncpy(this->stagingBuffer, term, sizeof(this->stagingBuffer) - 1); 505 | } 506 | 507 | void TinyGPSPlus::insertCustom(TinyGPSCustom *pElt, const char *sentenceName, int termNumber) 508 | { 509 | TinyGPSCustom **ppelt; 510 | 511 | for (ppelt = &this->customElts; *ppelt != NULL; ppelt = &(*ppelt)->next) 512 | { 513 | int cmp = strcmp(sentenceName, (*ppelt)->sentenceName); 514 | if (cmp < 0 || (cmp == 0 && termNumber < (*ppelt)->termNumber)) 515 | break; 516 | } 517 | 518 | pElt->next = *ppelt; 519 | *ppelt = pElt; 520 | } 521 | -------------------------------------------------------------------------------- /src/TinyGPS++.h: -------------------------------------------------------------------------------- 1 | /* 2 | TinyGPS++ - a small GPS library for Arduino providing universal NMEA parsing 3 | Based on work by and "distanceBetween" and "courseTo" courtesy of Maarten Lamers. 4 | Suggestion to add satellites, courseTo(), and cardinal() by Matt Monson. 5 | Location precision improvements suggested by Wayne Holder. 6 | Copyright (C) 2008-2024 Mikal Hart 7 | All rights reserved. 8 | 9 | This library is free software; you can redistribute it and/or 10 | modify it under the terms of the GNU Lesser General Public 11 | License as published by the Free Software Foundation; either 12 | version 2.1 of the License, or (at your option) any later version. 13 | 14 | This library is distributed in the hope that it will be useful, 15 | but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 17 | Lesser General Public License for more details. 18 | 19 | You should have received a copy of the GNU Lesser General Public 20 | License along with this library; if not, write to the Free Software 21 | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 22 | */ 23 | 24 | 25 | 26 | #ifndef __TinyGPSPlus_h 27 | #define __TinyGPSPlus_h 28 | 29 | #include 30 | #include "Arduino.h" 31 | #include 32 | 33 | #define _GPS_VERSION "1.1.0" // software version of this library 34 | #define _GPS_MPH_PER_KNOT 1.15077945 35 | #define _GPS_MPS_PER_KNOT 0.51444444 36 | #define _GPS_KMPH_PER_KNOT 1.852 37 | #define _GPS_MILES_PER_METER 0.00062137112 38 | #define _GPS_KM_PER_METER 0.001 39 | #define _GPS_FEET_PER_METER 3.2808399 40 | #define _GPS_MAX_FIELD_SIZE 15 41 | #define _GPS_EARTH_MEAN_RADIUS 6371009 // old: 6372795 42 | 43 | struct RawDegrees 44 | { 45 | uint16_t deg; 46 | uint32_t billionths; 47 | bool negative; 48 | public: 49 | RawDegrees() : deg(0), billionths(0), negative(false) 50 | {} 51 | }; 52 | 53 | struct TinyGPSLocation 54 | { 55 | friend class TinyGPSPlus; 56 | public: 57 | enum Quality { Invalid = '0', GPS = '1', DGPS = '2', PPS = '3', RTK = '4', FloatRTK = '5', Estimated = '6', Manual = '7', Simulated = '8' }; 58 | enum Mode { N = 'N', A = 'A', D = 'D', E = 'E'}; 59 | 60 | bool isValid() const { return valid; } 61 | bool isUpdated() const { return updated; } 62 | uint32_t age() const { return valid ? millis() - lastCommitTime : (uint32_t)ULONG_MAX; } 63 | const RawDegrees &rawLat() { updated = false; return rawLatData; } 64 | const RawDegrees &rawLng() { updated = false; return rawLngData; } 65 | double lat(); 66 | double lng(); 67 | Quality FixQuality() { updated = false; return fixQuality; } 68 | Mode FixMode() { updated = false; return fixMode; } 69 | 70 | TinyGPSLocation() : valid(false), updated(false), fixQuality(Invalid), fixMode(N) 71 | {} 72 | 73 | private: 74 | bool valid, updated; 75 | RawDegrees rawLatData, rawLngData, rawNewLatData, rawNewLngData; 76 | Quality fixQuality, newFixQuality; 77 | Mode fixMode, newFixMode; 78 | uint32_t lastCommitTime; 79 | void commit(); 80 | void setLatitude(const char *term); 81 | void setLongitude(const char *term); 82 | }; 83 | 84 | struct TinyGPSDate 85 | { 86 | friend class TinyGPSPlus; 87 | public: 88 | bool isValid() const { return valid; } 89 | bool isUpdated() const { return updated; } 90 | uint32_t age() const { return valid ? millis() - lastCommitTime : (uint32_t)ULONG_MAX; } 91 | 92 | uint32_t value() { updated = false; return date; } 93 | uint16_t year(); 94 | uint8_t month(); 95 | uint8_t day(); 96 | 97 | TinyGPSDate() : valid(false), updated(false), date(0) 98 | {} 99 | 100 | private: 101 | bool valid, updated; 102 | uint32_t date, newDate; 103 | uint32_t lastCommitTime; 104 | void commit(); 105 | void setDate(const char *term); 106 | }; 107 | 108 | struct TinyGPSTime 109 | { 110 | friend class TinyGPSPlus; 111 | public: 112 | bool isValid() const { return valid; } 113 | bool isUpdated() const { return updated; } 114 | uint32_t age() const { return valid ? millis() - lastCommitTime : (uint32_t)ULONG_MAX; } 115 | 116 | uint32_t value() { updated = false; return time; } 117 | uint8_t hour(); 118 | uint8_t minute(); 119 | uint8_t second(); 120 | uint8_t centisecond(); 121 | 122 | TinyGPSTime() : valid(false), updated(false), time(0) 123 | {} 124 | 125 | private: 126 | bool valid, updated; 127 | uint32_t time, newTime; 128 | uint32_t lastCommitTime; 129 | void commit(); 130 | void setTime(const char *term); 131 | }; 132 | 133 | struct TinyGPSDecimal 134 | { 135 | friend class TinyGPSPlus; 136 | public: 137 | bool isValid() const { return valid; } 138 | bool isUpdated() const { return updated; } 139 | uint32_t age() const { return valid ? millis() - lastCommitTime : (uint32_t)ULONG_MAX; } 140 | int32_t value() { updated = false; return val; } 141 | 142 | TinyGPSDecimal() : valid(false), updated(false), val(0) 143 | {} 144 | 145 | private: 146 | bool valid, updated; 147 | uint32_t lastCommitTime; 148 | int32_t val, newval; 149 | void commit(); 150 | void set(const char *term); 151 | }; 152 | 153 | struct TinyGPSInteger 154 | { 155 | friend class TinyGPSPlus; 156 | public: 157 | bool isValid() const { return valid; } 158 | bool isUpdated() const { return updated; } 159 | uint32_t age() const { return valid ? millis() - lastCommitTime : (uint32_t)ULONG_MAX; } 160 | uint32_t value() { updated = false; return val; } 161 | 162 | TinyGPSInteger() : valid(false), updated(false), val(0) 163 | {} 164 | 165 | private: 166 | bool valid, updated; 167 | uint32_t lastCommitTime; 168 | uint32_t val, newval; 169 | void commit(); 170 | void set(const char *term); 171 | }; 172 | 173 | struct TinyGPSSpeed : TinyGPSDecimal 174 | { 175 | double knots() { return value() / 100.0; } 176 | double mph() { return _GPS_MPH_PER_KNOT * value() / 100.0; } 177 | double mps() { return _GPS_MPS_PER_KNOT * value() / 100.0; } 178 | double kmph() { return _GPS_KMPH_PER_KNOT * value() / 100.0; } 179 | }; 180 | 181 | struct TinyGPSCourse : public TinyGPSDecimal 182 | { 183 | double deg() { return value() / 100.0; } 184 | }; 185 | 186 | struct TinyGPSAltitude : TinyGPSDecimal 187 | { 188 | double meters() { return value() / 100.0; } 189 | double miles() { return _GPS_MILES_PER_METER * value() / 100.0; } 190 | double kilometers() { return _GPS_KM_PER_METER * value() / 100.0; } 191 | double feet() { return _GPS_FEET_PER_METER * value() / 100.0; } 192 | }; 193 | 194 | struct TinyGPSHDOP : TinyGPSDecimal 195 | { 196 | double hdop() { return value() / 100.0; } 197 | }; 198 | 199 | class TinyGPSPlus; 200 | class TinyGPSCustom 201 | { 202 | public: 203 | TinyGPSCustom() {}; 204 | TinyGPSCustom(TinyGPSPlus &gps, const char *sentenceName, int termNumber); 205 | void begin(TinyGPSPlus &gps, const char *_sentenceName, int _termNumber); 206 | 207 | bool isUpdated() const { return updated; } 208 | bool isValid() const { return valid; } 209 | uint32_t age() const { return valid ? millis() - lastCommitTime : (uint32_t)ULONG_MAX; } 210 | const char *value() { updated = false; return buffer; } 211 | 212 | private: 213 | void commit(); 214 | void set(const char *term); 215 | 216 | char stagingBuffer[_GPS_MAX_FIELD_SIZE + 1]; 217 | char buffer[_GPS_MAX_FIELD_SIZE + 1]; 218 | unsigned long lastCommitTime; 219 | bool valid, updated; 220 | const char *sentenceName; 221 | int termNumber; 222 | friend class TinyGPSPlus; 223 | TinyGPSCustom *next; 224 | }; 225 | 226 | class TinyGPSPlus 227 | { 228 | public: 229 | TinyGPSPlus(); 230 | bool encode(char c); // process one character received from GPS 231 | TinyGPSPlus &operator << (char c) {encode(c); return *this;} 232 | 233 | TinyGPSLocation location; 234 | TinyGPSDate date; 235 | TinyGPSTime time; 236 | TinyGPSSpeed speed; 237 | TinyGPSCourse course; 238 | TinyGPSAltitude altitude; 239 | TinyGPSInteger satellites; 240 | TinyGPSHDOP hdop; 241 | 242 | static const char *libraryVersion() { return _GPS_VERSION; } 243 | 244 | static double distanceBetween(double lat1, double long1, double lat2, double long2); 245 | static double courseTo(double lat1, double long1, double lat2, double long2); 246 | static const char *cardinal(double course); 247 | 248 | static int32_t parseDecimal(const char *term); 249 | static void parseDegrees(const char *term, RawDegrees °); 250 | 251 | uint32_t charsProcessed() const { return encodedCharCount; } 252 | uint32_t sentencesWithFix() const { return sentencesWithFixCount; } 253 | uint32_t failedChecksum() const { return failedChecksumCount; } 254 | uint32_t passedChecksum() const { return passedChecksumCount; } 255 | 256 | private: 257 | enum {GPS_SENTENCE_GGA, GPS_SENTENCE_RMC, GPS_SENTENCE_OTHER}; 258 | 259 | // parsing state variables 260 | uint8_t parity; 261 | bool isChecksumTerm; 262 | char term[_GPS_MAX_FIELD_SIZE]; 263 | uint8_t curSentenceType; 264 | uint8_t curTermNumber; 265 | uint8_t curTermOffset; 266 | bool sentenceHasFix; 267 | 268 | // custom element support 269 | friend class TinyGPSCustom; 270 | TinyGPSCustom *customElts; 271 | TinyGPSCustom *customCandidates; 272 | void insertCustom(TinyGPSCustom *pElt, const char *sentenceName, int index); 273 | 274 | // statistics 275 | uint32_t encodedCharCount; 276 | uint32_t sentencesWithFixCount; 277 | uint32_t failedChecksumCount; 278 | uint32_t passedChecksumCount; 279 | 280 | // internal utilities 281 | int fromHex(char a); 282 | bool endOfTermHandler(); 283 | }; 284 | 285 | #endif // def(__TinyGPSPlus_h) 286 | -------------------------------------------------------------------------------- /src/TinyGPSPlus.h: -------------------------------------------------------------------------------- 1 | /* 2 | TinyGPSPlus - a small GPS library for Arduino providing universal NMEA parsing 3 | Based on work by and "distanceBetween" and "courseTo" courtesy of Maarten Lamers. 4 | Suggestion to add satellites, courseTo(), and cardinal() by Matt Monson. 5 | Location precision improvements suggested by Wayne Holder. 6 | Copyright (C) 2008-2024 Mikal Hart 7 | All rights reserved. 8 | 9 | This library is free software; you can redistribute it and/or 10 | modify it under the terms of the GNU Lesser General Public 11 | License as published by the Free Software Foundation; either 12 | version 2.1 of the License, or (at your option) any later version. 13 | 14 | This library is distributed in the hope that it will be useful, 15 | but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 17 | Lesser General Public License for more details. 18 | 19 | You should have received a copy of the GNU Lesser General Public 20 | License along with this library; if not, write to the Free Software 21 | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 22 | */ 23 | 24 | #ifndef __TinyGPSPlus_h 25 | #include "TinyGPS++.h" 26 | #endif --------------------------------------------------------------------------------