ESP_IOT v2.5
IOT ESP Coding
TinyGPS.cpp
Go to the documentation of this file.
1/*
2TinyGPS++ - a small GPS library for Arduino providing universal NMEA parsing
3Based on work by and "distanceBetween" and "courseTo" courtesy of Maarten Lamers.
4Suggestion to add satellites, courseTo(), and cardinal() by Matt Monson.
5Location precision improvements suggested by Wayne Holder.
6Copyright (C) 2008-2024 Mikal Hart
7All rights reserved.
8
9This library is free software; you can redistribute it and/or
10modify it under the terms of the GNU Lesser General Public
11License as published by the Free Software Foundation; either
12version 2.1 of the License, or (at your option) any later version.
13
14This library is distributed in the hope that it will be useful,
15but WITHOUT ANY WARRANTY; without even the implied warranty of
16MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17Lesser General Public License for more details.
18
19You should have received a copy of the GNU Lesser General Public
20License along with this library; if not, write to the Free Software
21Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
22*/
23
24#include "TinyGPS.h"
25
26#include <string.h>
27#include <ctype.h>
28#include <stdlib.h>
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
35unsigned 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<std::chrono::milliseconds>(end_time - start_time);
41
42 return static_cast<unsigned long>(duration.count());
43}
44#endif
45
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
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//
114int 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
126int32_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
143void TinyGPSPlus::parseDegrees(const char *term, RawDegrees &deg)
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
170bool 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 (strchr("GB", term[0]) && strchr("PNABLD", term[1]) != NULL && !strcmp(term + 2, _RMCterm))
224 curSentenceType = GPS_SENTENCE_RMC;
225 else if (strchr("GB", term[0]) && strchr("PNABLD", 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 */
300double 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
325double 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
345const 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
352void TinyGPSLocation::commit()
353{
354 rawLatData = rawNewLatData;
355 rawLngData = rawNewLngData;
356 fixQuality = newFixQuality;
357 fixMode = newFixMode;
358 lastCommitTime = millis();
359 valid = updated = true;
360}
361
362void TinyGPSLocation::setLatitude(const char *term)
363{
364 TinyGPSPlus::parseDegrees(term, rawNewLatData);
365}
366
367void TinyGPSLocation::setLongitude(const char *term)
368{
369 TinyGPSPlus::parseDegrees(term, rawNewLngData);
370}
371
373{
374 updated = false;
375 double ret = rawLatData.deg + rawLatData.billionths / 1000000000.0;
376 return rawLatData.negative ? -ret : ret;
377}
378
380{
381 updated = false;
382 double ret = rawLngData.deg + rawLngData.billionths / 1000000000.0;
383 return rawLngData.negative ? -ret : ret;
384}
385
386void TinyGPSDate::commit()
387{
388 date = newDate;
389 lastCommitTime = millis();
390 valid = updated = true;
391}
392
393void TinyGPSTime::commit()
394{
395 time = newTime;
396 lastCommitTime = millis();
397 valid = updated = true;
398}
399
400void TinyGPSTime::setTime(const char *term)
401{
402 newTime = (uint32_t)TinyGPSPlus::parseDecimal(term);
403}
404
405void TinyGPSDate::setDate(const char *term)
406{
407 newDate = atol(term);
408}
409
411{
412 updated = false;
413 uint16_t year = date % 100;
414 return year + 2000;
415}
416
418{
419 updated = false;
420 return (date / 100) % 100;
421}
422
424{
425 updated = false;
426 return date / 10000;
427}
428
430{
431 updated = false;
432 return time / 1000000;
433}
434
436{
437 updated = false;
438 return (time / 10000) % 100;
439}
440
442{
443 updated = false;
444 return (time / 100) % 100;
445}
446
448{
449 updated = false;
450 return time % 100;
451}
452
453void TinyGPSDecimal::commit()
454{
455 val = newval;
456 lastCommitTime = millis();
457 valid = updated = true;
458}
459
460void TinyGPSDecimal::set(const char *term)
461{
462 newval = TinyGPSPlus::parseDecimal(term);
463}
464
465void TinyGPSInteger::commit()
466{
467 val = newval;
468 lastCommitTime = millis();
469 valid = updated = true;
470}
471
472void TinyGPSInteger::set(const char *term)
473{
474 newval = atol(term);
475}
476
477TinyGPSCustom::TinyGPSCustom(TinyGPSPlus &gps, const char *_sentenceName, int _termNumber)
478{
479 begin(gps, _sentenceName, _termNumber);
480}
481
482void 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
495void TinyGPSCustom::commit()
496{
497 strcpy(this->buffer, this->stagingBuffer);
498 lastCommitTime = millis();
499 valid = updated = true;
500}
501
502void TinyGPSCustom::set(const char *term)
503{
504 strncpy(this->stagingBuffer, term, sizeof(this->stagingBuffer) - 1);
505}
506
507void 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}
MultipleSatellite gps(Serial1, GPSBaud, SERIAL_8N1, TinyGPS_RXPin, TinyGPS_TXPin)
unsigned long millis()
Definition: TinyGPS.cpp:35
#define _RMCterm
Definition: TinyGPS.cpp:30
#define _GGAterm
Definition: TinyGPS.cpp:31
#define COMBINE(sentence_type, term_number)
Definition: TinyGPS.cpp:166
#define _GPS_EARTH_MEAN_RADIUS
Definition: TinyGPS.h:41
void begin(TinyGPSPlus &gps, const char *_sentenceName, int _termNumber)
Definition: TinyGPS.cpp:482
static void parseDegrees(const char *term, RawDegrees &deg)
Definition: TinyGPS.cpp:143
static int32_t parseDecimal(const char *term)
Definition: TinyGPS.cpp:126
TinyGPSAltitude altitude
Definition: TinyGPS.h:238
TinyGPSHDOP hdop
Definition: TinyGPS.h:240
TinyGPSTime time
Definition: TinyGPS.h:235
TinyGPSInteger satellites
Definition: TinyGPS.h:239
TinyGPSDate date
Definition: TinyGPS.h:234
TinyGPSLocation location
Definition: TinyGPS.h:233
static const char * cardinal(double course)
Definition: TinyGPS.cpp:345
TinyGPSSpeed speed
Definition: TinyGPS.h:236
static double distanceBetween(double lat1, double long1, double lat2, double long2)
Definition: TinyGPS.cpp:300
TinyGPSCourse course
Definition: TinyGPS.h:237
bool encode(char c)
Definition: TinyGPS.cpp:67
static double courseTo(double lat1, double long1, double lat2, double long2)
Definition: TinyGPS.cpp:325
uint16_t deg
Definition: TinyGPS.h:45
uint32_t billionths
Definition: TinyGPS.h:46
bool negative
Definition: TinyGPS.h:47
uint8_t month()
Definition: TinyGPS.cpp:417
uint16_t year()
Definition: TinyGPS.cpp:410
uint8_t day()
Definition: TinyGPS.cpp:423
double lng()
Definition: TinyGPS.cpp:379
double lat()
Definition: TinyGPS.cpp:372
uint8_t centisecond()
Definition: TinyGPS.cpp:447
uint8_t hour()
Definition: TinyGPS.cpp:429
uint8_t second()
Definition: TinyGPS.cpp:441
uint8_t minute()
Definition: TinyGPS.cpp:435