esp32_Adafruit_GPS/src/NMEA_parse.cpp

852 lines
29 KiB
C++
Raw Normal View History

/**************************************************************************/
/*!
@file NMEA_parse.cpp
@mainpage Adafruit Ultimate GPS Breakout
@section intro Introduction
This is the Adafruit GPS library - the ultimate GPS library
for the ultimate GPS module!
Tested and works great with the Adafruit Ultimate GPS module
using MTK33x9 chipset
------> http://www.adafruit.com/products/746
Adafruit invests time and resources providing this open source code,
please support Adafruit and open-source hardware by purchasing
products from Adafruit!
@section author Author
Written by Limor Fried/Ladyada for Adafruit Industries.
@section license License
BSD license, check license.txt for more information
All text above must be included in any redistribution
*/
/**************************************************************************/
#include <Adafruit_GPS.h>
/**************************************************************************/
/*!
@brief Parse a standard NMEA string and update the relevant variables.
Sentences start with a $, then a two character source identifier, then a
three character sentence identifier that defines the format, then a comma and
more comma separated fields defined by the sentence name. There are many
sentences listed that are not yet supported, including proprietary sentences
that start with P, like the $PMTK commands to the GPS modules. See the
build() function and http://fort21.ru/download/NMEAdescription.pdf for
sentence descriptions.
2020-01-29 17:57:20 +00:00
Encapsulated data sentences are supported by NMEA-183, and start with !
instead of $. https://gpsd.gitlab.io/gpsd/AIVDM.html provides details
about encapsulated data sentences used in AIS.
parse() permits, but does not require Carriage Return and Line Feed at the
end of sentences. The end of the sentence is recognized by the * for the
checksum. parse() will not recognize a sentence without a valid checksum.
NMEA_EXTENSIONS must be defined in order to parse more than basic
GPS module sentences.
@param nmea Pointer to the NMEA string
@return True if successfully parsed, false if fails check or parsing
*/
/**************************************************************************/
bool Adafruit_GPS::parse(char *nmea) {
if (!check(nmea))
return false;
// passed the check, so there's a valid source in thisSource and a valid
// sentence in thisSentence
char *p = nmea; // Pointer to move through the sentence -- good parsers are
// non-destructive
p = strchr(p, ',') + 1; // Skip to char after the next comma, then check.
// This may look inefficient, but an M0 will get down the list in about 1 us /
// strcmp()! Put the GPS sentences from Adafruit_GPS at the top to make
// pruning excess code easier. Otherwise, keep them alphabetical for ease of
// reading.
if (!strcmp(thisSentence, "GGA")) { //************************************GGA
// Adafruit from Actisense NGW-1 from SH CP150C
parseTime(p);
p = strchr(p, ',') + 1; // parse time with specialized function
// parse out both latitude and direction, then go to next field, or fail
if (parseCoord(p, &latitudeDegrees, &latitude, &latitude_fixed, &lat))
newDataValue(NMEA_LAT, latitudeDegrees);
p = strchr(p, ',') + 1;
p = strchr(p, ',') + 1;
// parse out both longitude and direction, then go to next field, or fail
if (parseCoord(p, &longitudeDegrees, &longitude, &longitude_fixed, &lon))
newDataValue(NMEA_LON, longitudeDegrees);
p = strchr(p, ',') + 1;
p = strchr(p, ',') + 1;
if (!isEmpty(p)) { // if it's a , (or a * at end of sentence) the value is
// not included
fixquality = atoi(p); // needs additional processing
if (fixquality > 0) {
fix = true;
lastFix = sentTime;
} else
fix = false;
}
p = strchr(p, ',') + 1; // then move on to the next
// Most can just be parsed with atoi() or atof(), then move on to the next.
if (!isEmpty(p))
satellites = atoi(p);
p = strchr(p, ',') + 1;
if (!isEmpty(p))
newDataValue(NMEA_HDOP, HDOP = atof(p));
p = strchr(p, ',') + 1;
if (!isEmpty(p))
altitude = atof(p);
p = strchr(p, ',') + 1;
p = strchr(p, ',') + 1; // skip the units
if (!isEmpty(p))
geoidheight = atof(p); // skip the rest
} else if (!strcmp(thisSentence, "RMC")) { //*****************************RMC
// in Adafruit from Actisense NGW-1 from SH CP150C
parseTime(p);
p = strchr(p, ',') + 1;
parseFix(p);
p = strchr(p, ',') + 1;
// parse out both latitude and direction, then go to next field, or fail
if (parseCoord(p, &latitudeDegrees, &latitude, &latitude_fixed, &lat))
newDataValue(NMEA_LAT, latitudeDegrees);
p = strchr(p, ',') + 1;
p = strchr(p, ',') + 1;
// parse out both longitude and direction, then go to next field, or fail
if (parseCoord(p, &longitudeDegrees, &longitude, &longitude_fixed, &lon))
newDataValue(NMEA_LON, longitudeDegrees);
p = strchr(p, ',') + 1;
p = strchr(p, ',') + 1;
if (!isEmpty(p))
newDataValue(NMEA_SOG, speed = atof(p));
p = strchr(p, ',') + 1;
if (!isEmpty(p))
newDataValue(NMEA_COG, angle = atof(p));
p = strchr(p, ',') + 1;
if (!isEmpty(p)) {
uint32_t fulldate = atof(p);
day = fulldate / 10000;
month = (fulldate % 10000) / 100;
year = (fulldate % 100);
lastDate = sentTime;
} // skip the rest
} else if (!strcmp(thisSentence, "GLL")) { //*****************************GLL
// in Adafruit from Actisense NGW-1 from SH CP150C
// parse out both latitude and direction, then go to next field, or fail
if (parseCoord(p, &latitudeDegrees, &latitude, &latitude_fixed, &lat))
newDataValue(NMEA_LAT, latitudeDegrees);
p = strchr(p, ',') + 1;
p = strchr(p, ',') + 1;
// parse out both longitude and direction, then go to next field, or fail
if (parseCoord(p, &longitudeDegrees, &longitude, &longitude_fixed, &lon))
newDataValue(NMEA_LON, longitudeDegrees);
p = strchr(p, ',') + 1;
p = strchr(p, ',') + 1;
parseTime(p);
p = strchr(p, ',') + 1;
parseFix(p); // skip the rest
} else if (!strcmp(thisSentence, "GSA")) { //*****************************GSA
// in Adafruit from Actisense NGW-1
p = strchr(p, ',') + 1; // skip selection mode
if (!isEmpty(p))
fixquality_3d = atoi(p);
p = strchr(p, ',') + 1;
// skip 12 Satellite PDNs without interpreting them
for (int i = 0; i < 12; i++)
p = strchr(p, ',') + 1;
if (!isEmpty(p))
PDOP = atof(p);
p = strchr(p, ',') + 1;
// parse out HDOP, we also parse this from the GGA sentence. Chipset should
// report the same for both
if (!isEmpty(p))
newDataValue(NMEA_HDOP, HDOP = atof(p));
p = strchr(p, ',') + 1;
if (!isEmpty(p))
VDOP = atof(p); // last before checksum
}
#ifdef NMEA_EXTENSIONS // Sentences not required for basic GPS functionality
else if (!strcmp(thisSentence, "APB")) { //*******************************APB
// from Actisense NGW-1 from SH CP150C
return false;
} else if (!strcmp(thisSentence, "DBT")) { //*****************************DBT
// from Actisense NGW-1
// feet, metres, fathoms below transducer coerced to water depth from
// surface in metres
if (!isEmpty(p))
newDataValue(NMEA_DEPTH, atof(p) * 0.3048 + depthToTransducer);
p = strchr(p, ',') + 1;
p = strchr(p, ',') + 1;
if (!isEmpty(p))
newDataValue(NMEA_DEPTH, atof(p) + depthToTransducer);
p = strchr(p, ',') + 1;
p = strchr(p, ',') + 1;
if (!isEmpty(p))
newDataValue(NMEA_DEPTH, atof(p) * 6 * 0.3048 + depthToTransducer);
} else if (!strcmp(thisSentence, "DPT")) { //*****************************DPT
// from Actisense NGW-1
return false;
} else if (!strcmp(thisSentence, "GSV")) { //*****************************GSV
// from Actisense NGW-1
return false;
} else if (!strcmp(thisSentence, "HDG")) { //*****************************HDG
// from Actisense NGW-1 from SH CP150C
return false;
} else if (!strcmp(thisSentence, "HDM")) { //*****************************HDM
if (!isEmpty(p))
newDataValue(NMEA_HDG, atof(p)); // skip the rest
} else if (!strcmp(thisSentence, "HDT")) { //*****************************HDT
if (!isEmpty(p))
newDataValue(NMEA_HDT, atof(p)); // skip the rest
} else if (!strcmp(thisSentence, "MDA")) { //*****************************MDA
// from Actisense NGW-1
if (!isEmpty(p))
newDataValue(NMEA_BAROMETER, atof(p) * 3386.39);
p = strchr(p, ',') + 1;
p = strchr(p, ',') + 1;
if (!isEmpty(p))
newDataValue(NMEA_BAROMETER, atof(p) * 100000);
p = strchr(p, ',') + 1;
p = strchr(p, ',') + 1;
nmea_float_t T = 100000.;
char u = 'C';
if (!isEmpty(p))
T = atof(p);
p = strchr(p, ',') + 1;
if (!isEmpty(p))
u = *p;
p = strchr(p, ',') + 1;
if (u != 'C') {
T = (T - 32) / 1.8;
u = 'C';
} // coerce to C
if (T < 1000)
newDataValue(NMEA_TEMPERATURE_AIR, T);
T = 100000.;
u = 'C';
if (!isEmpty(p))
T = atof(p);
p = strchr(p, ',') + 1;
if (!isEmpty(p))
u = *p;
p = strchr(p, ',') + 1;
if (u != 'C') {
T = (T - 32) / 1.8;
u = 'C';
}
if (T < 1000)
newDataValue(NMEA_TEMPERATURE_WATER, T);
if (!isEmpty(p))
newDataValue(NMEA_HUMIDITY, atof(p)); // skip the rest
} else if (!strcmp(thisSentence, "MTW")) { //*****************************MTW
nmea_float_t T = 100000.;
char u = 'C';
if (!isEmpty(p))
T = atof(p);
p = strchr(p, ',') + 1;
if (!isEmpty(p))
u = *p; // last before checksum
if (u != 'C') {
T = (T - 32) / 1.8;
u = 'C';
}
if (T < 1000)
newDataValue(NMEA_TEMPERATURE_WATER, T);
} else if (!strcmp(thisSentence, "MWD")) { //*****************************MWD
// from Actisense NGW-1
return false;
} else if (!strcmp(thisSentence, "MWV")) { //*****************************MWV
// from Actisense NGW-1
nmea_float_t ang = 100000.;
char ref = 'T';
if (!isEmpty(p))
ang = atof(p);
p = strchr(p, ',') + 1;
if (!isEmpty(p))
ref = *p;
p = strchr(p, ',') + 1;
nmea_float_t spd = 100000.;
if (!isEmpty(p))
spd = atof(p);
p = strchr(p, ',') + 1;
char units = 'N';
if (!isEmpty(p))
units = *p;
p = strchr(p, ',') + 1;
char stat = 'A';
if (!isEmpty(p))
stat = *p; // last before checksum
if (units == 'K') {
spd /= 1.6;
units = 'M';
}
if (units == 'M') {
spd *= 5280. / 6000.;
units = 'N';
}
if (ang > 180.)
ang -= 360.;
if (ref == 'R') {
if (ang < 1000. && stat == 'A')
newDataValue(NMEA_AWA, ang);
if (spd < 1000. && stat == 'A')
newDataValue(NMEA_AWS, spd);
} else {
if (ang < 1000. && stat == 'A')
newDataValue(NMEA_TWA, ang);
if (spd < 1000. && stat == 'A')
newDataValue(NMEA_TWS, spd);
}
} else if (!strcmp(thisSentence, "RMB")) { //*****************************RMB
// from Actisense NGW-1 from SH CP150C
2020-02-07 19:54:26 +00:00
// RMB Recommended Minimum Navigation Information
// 1 2 3 4 5 6 7 8 9 10 11 12 13 14
// | | | | | | | | | | | | | |
//$--RMB,A,x.x,a,c--c,c--c,llll.ll,a,yyyyy.yy,a,x.x,x.x,x.x,A*hh
// 1) Status, V = Navigation receiver warning
// 2) Cross Track error - nautical miles
// 3) Direction to Steer, Left or Right
// 4) TO Waypoint ID
// 5) FROM Waypoint ID
// 6) Destination Waypoint Latitude 7) N or S
// 8) Destination Waypoint Longitude 9) E or W
// 10) Range to destination in nautical miles
// 11) Bearing to destination in degrees True
// 12) Destination closing velocity in knots
// 13) Arrival Status, A = Arrival Circle Entered 14) Checksum
p = strchr(p, ',') + 1; // skip status
nmea_float_t xte = 100000.;
char xteDir = 'X';
if (!isEmpty(p))
xte = atof(p);
p = strchr(p, ',') + 1;
if (!isEmpty(p))
xteDir = *p;
p = strchr(p, ',') + 1;
if (xte < 10000. && xteDir != 'X') {
if (xteDir == 'L')
xte *= -1.;
newDataValue(NMEA_XTE, xte);
}
if (!isEmpty(p))
parseStr(toID, p, NMEA_MAX_WP_ID);
p = strchr(p, ',') + 1;
if (!isEmpty(p))
parseStr(fromID, p, NMEA_MAX_WP_ID);
p = strchr(p, ',') + 1;
2020-02-07 19:54:26 +00:00
nmea_float_t latitudeWP = 0;
nmea_float_t longitudeWP = 0;
int32_t latitude_fixedWP = 0;
int32_t longitude_fixedWP = 0;
nmea_float_t latitudeDegreesWP = 0;
nmea_float_t longitudeDegreesWP = 0;
2020-02-07 19:54:26 +00:00
char latWP = 'X';
char lonWP = 'X';
// parse out both latitude and direction for WayPoint, then go to next
// field, or fail
if (!isEmpty(p)) {
if (!parseCoord(p, &latitudeDegreesWP, &latitudeWP, &latitude_fixedWP,
&latWP))
return false;
else
newDataValue(NMEA_LATWP, latitudeDegreesWP);
}
p = strchr(p, ',') + 1;
p = strchr(p, ',') + 1;
// parse out both longitude and direction for WayPoint, then go to next
// field, or fail
if (!isEmpty(p)) {
if (!parseCoord(p, &longitudeDegreesWP, &longitudeWP, &longitude_fixedWP,
&lonWP))
return false;
else
newDataValue(NMEA_LONWP, longitudeDegreesWP);
}
p = strchr(p, ',') + 1;
p = strchr(p, ',') + 1;
if (!isEmpty(p))
newDataValue(NMEA_DISTWP, atof(p));
p = strchr(p, ',') + 1;
if (!isEmpty(p))
newDataValue(NMEA_COGWP, atof(p));
p = strchr(p, ',') + 1;
if (!isEmpty(p))
newDataValue(NMEA_VMGWP, atof(p)); // skip arrival flag
} else if (!strcmp(thisSentence, "ROT")) { //*****************************ROT
return false;
} else if (!strcmp(thisSentence, "RPM")) { //*****************************RPM
return false;
} else if (!strcmp(thisSentence, "RSA")) { //*****************************RSA
// from Actisense NGW-1
return false;
} else if (!strcmp(thisSentence, "TXT")) { //*****************************TXT
if (!isEmpty(p))
txtTot = atoi(p);
p = strchr(p, ',') + 1;
if (!isEmpty(p))
txtN = atoi(p);
p = strchr(p, ',') + 1;
if (!isEmpty(p))
txtID = atoi(p);
p = strchr(p, ',') + 1;
if (!isEmpty(p))
parseStr(txtTXT, p, 61); // copy the text to NMEA TXT max of 61 characters
} else if (!strcmp(thisSentence, "VDR")) { //*****************************VDR
// from Actisense NGW-1
return false;
} else if (!strcmp(thisSentence, "VHW")) { //*****************************VHW
// from Actisense NGW-1
if (!isEmpty(p))
newDataValue(NMEA_HDT, atof(p));
p = strchr(p, ',') + 1;
p = strchr(p, ',') + 1;
if (!isEmpty(p))
newDataValue(NMEA_HDG, atof(p));
p = strchr(p, ',') + 1;
p = strchr(p, ',') + 1;
if (!isEmpty(p))
newDataValue(NMEA_VTW, atof(p)); // skip the other units
} else if (!strcmp(thisSentence, "VLW")) { //*****************************VLW
// from Actisense NGW-1
if (!isEmpty(p))
newDataValue(NMEA_LOG, atof(p));
p = strchr(p, ',') + 1;
p = strchr(p, ',') + 1;
if (!isEmpty(p))
newDataValue(NMEA_LOGR, atof(p)); // skip the other units
} else if (!strcmp(thisSentence, "VPW")) { //*****************************VPW
// knots, metres/s coerced to knots
nmea_float_t vmg = 100000.;
if (!isEmpty(p))
vmg = atof(p);
p = strchr(p, ',') + 1;
p = strchr(p, ',') + 1;
if (!isEmpty(p))
vmg = atof(p) * 0.3048 * 3600. / 6000.; // skip units
if (vmg < 1000.)
newDataValue(NMEA_VMG, vmg);
} else if (!strcmp(thisSentence, "VTG")) { //*****************************VTG
// from Actisense NGW-1 from SH CP150C
return false;
} else if (!strcmp(thisSentence, "VWR")) { //*****************************VWR
// from Actisense NGW-1
nmea_float_t ang = 1000.;
if (!isEmpty(p))
ang = atof(p);
p = strchr(p, ',') + 1;
char ref = ' ';
if (!isEmpty(p))
ref = *p;
p = strchr(p, ',') + 1;
if (ref == 'L')
ang *= -1;
if (ang < 1000.)
newDataValue(NMEA_AWA, ang);
nmea_float_t ws = 0.0;
char units = 'X';
if (!isEmpty(p))
ws = atof(p);
p = strchr(p, ',') + 1; // knots
if (!isEmpty(p))
units = *p;
p = strchr(p, ',') + 1;
if (!isEmpty(p))
ws = atof(p);
p = strchr(p, ',') + 1; // meters / second
if (!isEmpty(p))
units = *p;
p = strchr(p, ',') + 1; // M
if (!isEmpty(p))
ws = atof(p);
p = strchr(p, ',') + 1; // kilometers / hour can be converted back to knots
if (!isEmpty(p))
units = *p; // last before checksum
if (units == 'M') {
ws *= 3.6;
units = 'K';
} // convert m/s to km/h
if (units == 'K') {
ws /= 1.6;
units = 'M';
} // convert km/h to miles / h
if (units == 'M') {
ws *= 5280. / 6000.;
units = 'N';
} // convert miles / hr to knots
if (units == 'N')
newDataValue(NMEA_AWS, ws); // store the final result
} else if (!strcmp(thisSentence, "WCV")) { //*****************************WCV
// from SH CP150C
if (!isEmpty(p))
newDataValue(NMEA_VMGWP, atof(p)); // skip the rest
} else if (!strcmp(thisSentence, "XTE")) { //*****************************XTE
// from Actisense NGW-1 from SH CP150C
p = strchr(p, ',') + 1; // skip status 1
p = strchr(p, ',') + 1; // skip status 2
nmea_float_t xte = 100000.;
char xteDir = 'X';
if (!isEmpty(p))
xte = atof(p);
p = strchr(p, ',') + 1;
if (!isEmpty(p))
xteDir = *p;
p = strchr(p, ',') + 1;
if (xte < 10000. && xteDir != 'X') {
if (xteDir == 'L')
xte *= -1.;
newDataValue(NMEA_XTE, xte);
} // skip units
} else if (!strcmp(thisSentence, "ZDA")) { //*****************************ZDA
// from Actisense NGW-1
return false;
}
#endif // NMEA_EXTENSIONS
else {
return false; // didn't find the required sentence definition
}
// Record the successful parsing of where the last data came from and when
strcpy(lastSource, thisSource);
strcpy(lastSentence, thisSentence);
lastUpdate = millis();
return true;
}
/**************************************************************************/
/*!
@brief Check an NMEA string for basic format, valid source ID and valid
and valid sentence ID. Update the values of thisCheck, thisSource and
thisSentence.
@param nmea Pointer to the NMEA string
@return True if well formed, false if it has problems
*/
/**************************************************************************/
boolean Adafruit_GPS::check(char *nmea) {
thisCheck = 0; // new check
*thisSentence = *thisSource = 0;
if (*nmea != '$' && *nmea != '!')
return false; // doesn't start with $ or !
else
thisCheck += NMEA_HAS_DOLLAR;
// do checksum check -- first look if we even have one -- ignore all but last
// *
char *ast = nmea; // not strchr(nmea,'*'); for first *
while (*ast)
ast++; // go to the end
while (*ast != '*' && ast > nmea)
ast--; // then back to * if it's there
if (*ast != '*')
return false; // there is no asterisk
else {
uint16_t sum = parseHex(*(ast + 1)) * 16; // extract checksum
sum += parseHex(*(ast + 2));
char *p = nmea; // check checksum
for (char *p1 = p + 1; p1 < ast; p1++)
sum ^= *p1;
if (sum != 0)
return false; // bad checksum :(
else
thisCheck += NMEA_HAS_CHECKSUM;
}
// extract source of variable length
char *p = nmea + 1;
const char *src = tokenOnList(p, sources);
if (src) {
strcpy(thisSource, src);
thisCheck += NMEA_HAS_SOURCE;
} else
return false;
p += strlen(src);
// extract sentence id and check if parsed
const char *snc = tokenOnList(p, sentences_parsed);
if (snc) {
strcpy(thisSentence, snc);
thisCheck += NMEA_HAS_SENTENCE_P + NMEA_HAS_SENTENCE;
} else { // check if known
snc = tokenOnList(p, sentences_known);
if (snc) {
strcpy(thisSentence, snc);
thisCheck += NMEA_HAS_SENTENCE;
return false; // known but not parsed
} else {
parseStr(thisSentence, p, NMEA_MAX_SENTENCE_ID);
return false; // unknown
}
}
return true; // passed all the tests
}
/**************************************************************************/
/*!
@brief Check if a token at the start of a string is on a list.
@param token Pointer to the string
@param list A list of strings, with the final entry starting "ZZ"
@return Pointer to the found token, or NULL if it fails
*/
/**************************************************************************/
const char *Adafruit_GPS::tokenOnList(char *token, const char **list) {
int i = 0; // index in the list
while (strncmp(list[i], "ZZ", 2) &&
i < 1000) { // stop at terminator and don't crash without it
// test for a match on the sentence name
if (!strncmp((const char *)list[i], (const char *)token, strlen(list[i])))
return list[i];
i++;
}
return NULL; // couldn't find a match
}
/**************************************************************************/
/*!
@brief Check if an NMEA string is valid and is on a list, perhaps to
decide if it should be passed to a particular NMEA device.
@param nmea Pointer to the NMEA string
@param list A list of strings, with the final entry "ZZ"
@return True if on the list, false if it fails check or is not on the list
*/
/**************************************************************************/
bool Adafruit_GPS::onList(char *nmea, const char **list) {
if (!check(nmea)) // sets thisSentence if valid
return false; // not a valid sentence
// stop at terminator with first two letters ZZ and don't crash without it
for (int i = 0; strncmp(list[i], "ZZ", 2) && i < 1000; i++) {
// test for a match on the sentence name
if (!strcmp((const char *)list[i], (const char *)thisSentence))
return true;
}
return false; // couldn't find a match
}
/**************************************************************************/
/*!
@brief Parse a part of an NMEA string for lat or lon angle and direction.
Works for either DDMM.mmmm,N (latitude) or DDDMM.mmmm,W (longitude) format.
Insensitive to number of decimal places present. Only fills the variables
if it succeeds and the variable pointer is not NULL. This allows calling
2020-01-29 17:57:20 +00:00
to fill only the variables of interest. Does rudimentary validation on
angle range.
Supersedes private functions parseLat(), parseLon(), parseLatDir(),
parseLonDir(), all previously called from parse().
@param pStart Pointer to the location of the token in the NMEA string
@param angle Pointer to the angle to fill with value in degrees/minutes as
received from the GPS (DDDMM.MMMM), unsigned
@param angle_fixed Pointer to the fix point version latitude in decimal
degrees * 10000000, signed
@param angleDegrees Pointer to the angle to fill with decimal degrees,
2020-01-29 17:57:20 +00:00
signed. As actual double on SAMD, etc. resolution is better than the
fixed point version.
@param dir Pointer to character to fill the direction N/S/E/W
@return true if successful, false if failed or no value
*/
/**************************************************************************/
bool Adafruit_GPS::parseCoord(char *pStart, nmea_float_t *angleDegrees,
nmea_float_t *angle, int32_t *angle_fixed,
char *dir) {
char *p = pStart;
2020-01-29 17:57:20 +00:00
if (!isEmpty(p)) {
// get the number in DDDMM.mmmm format and break into components
char degreebuff[10];
char *e = strchr(p, '.');
if (e == NULL || e - p > 6)
return false; // no decimal point in range
strncpy(degreebuff, p, e - p); // get DDDMM
long dddmm = atol(degreebuff);
long degrees = (dddmm / 100); // truncate the minutes
long minutes = dddmm - degrees * 100; // remove the degrees
p = e; // start from the decimal point
nmea_float_t decminutes = atof(e); // the fraction after the decimal point
2020-01-29 17:57:20 +00:00
p = strchr(p, ',') + 1; // go to the next field
// get the NSEW direction as a character
char nsew = 'X';
if (!isEmpty(p))
nsew = *p; // field is not empty
else
return false; // no direction provided
// set the various numerical formats to their values
long fixed = degrees * 10000000 + (minutes * 10000000) / 60 +
(decminutes * 10000000) / 60;
nmea_float_t ang = degrees * 100 + minutes + decminutes;
nmea_float_t deg = fixed / (nmea_float_t)10000000.;
if (nsew == 'S' ||
nsew == 'W') { // fixed and deg are signed, but DDDMM.mmmm is not
fixed = -fixed;
deg = -deg;
}
// reject directions that are not NSEW
if (nsew != 'N' && nsew != 'S' && nsew != 'E' && nsew != 'W')
return false;
2020-01-29 17:57:20 +00:00
// reject angles that are out of range
if (nsew == 'N' || nsew == 'S')
2020-01-29 18:23:02 +00:00
if (abs(deg) > 90)
2020-01-29 17:57:20 +00:00
return false;
2020-01-29 18:23:02 +00:00
if (abs(deg) > 180)
2020-01-29 17:57:20 +00:00
return false;
// store in locations passed as args
if (angle != NULL)
*angle = ang;
if (angle_fixed != NULL)
*angle_fixed = fixed;
if (angleDegrees != NULL)
*angleDegrees = deg;
if (dir != NULL)
*dir = nsew;
} else
return false; // no number
return true;
}
/**************************************************************************/
/*!
@brief Parse a string token from pointer p to the next comma, asterisk
or end of string.
@param buff Pointer to the buffer to store the string in
@param p Pointer into a string
@param n Max permitted size of string including terminating 0
@return Pointer to the string buffer
*/
/**************************************************************************/
char *Adafruit_GPS::parseStr(char *buff, char *p, int n) {
char *e = strchr(p, ',');
int len = 0;
if (e) {
len = min(e - p, n - 1);
strncpy(buff, p, len); // copy up to the comma
buff[len] = 0;
} else {
e = strchr(p, '*');
if (e) {
len = min(e - p, n - 1);
strncpy(buff, p, len); // or up to the *
buff[e - p] = 0;
} else {
len = min((int)strlen(p), n - 1);
strncpy(buff, p, len); // or to the end or max capacity
}
}
return buff;
}
/**************************************************************************/
/*!
@brief Parse a part of an NMEA string for time. Independent of number
of decimal places after the '.'
@param p Pointer to the location of the token in the NMEA string
@return true if successful, false otherwise
*/
/**************************************************************************/
bool Adafruit_GPS::parseTime(char *p) {
if (!isEmpty(p)) { // get time
uint32_t time = atol(p);
hour = time / 10000;
minute = (time % 10000) / 100;
seconds = (time % 100);
char *dec = strchr(p, '.');
char *comstar = min(strchr(p, ','), strchr(p, '*'));
if(dec != NULL && comstar != NULL && dec < comstar)
milliseconds = atof(p) * 1000;
else milliseconds = 0;
lastTime = sentTime;
return true;
}
return false;
}
/**************************************************************************/
/*!
@brief Parse a part of an NMEA string for whether there is a fix
@param p Pointer to the location of the token in the NMEA string
@return True if we parsed it, false if it has invalid data
*/
/**************************************************************************/
boolean Adafruit_GPS::parseFix(char *p) {
if (!isEmpty(p)) {
if (p[0] == 'A') {
fix = true;
lastFix = sentTime;
} else if (p[0] == 'V')
fix = false;
else
return false;
return true;
}
return false;
}
/**************************************************************************/
/*!
@brief Is the field empty, or should we try conversion? Won't work
for a text field that starts with an asterisk or a comma, but that
probably violates the NMEA-183 standard.
@param pStart Pointer to the location of the token in the NMEA string
@return true if empty field, false if something there
*/
/**************************************************************************/
bool Adafruit_GPS::isEmpty(char *pStart) {
if (',' != *pStart && '*' != *pStart && pStart != NULL)
return false;
else
return true;
}
/**************************************************************************/
/*!
@brief Parse a hex character and return the appropriate decimal value
@param c Hex character, e.g. '0' or 'B'
@return Integer value of the hex character. Returns 0 if c is not a proper
character
*/
/**************************************************************************/
// read a Hex value and return the decimal equivalent
uint8_t Adafruit_GPS::parseHex(char c) {
if (c < '0')
return 0;
if (c <= '9')
return c - '0';
if (c < 'A')
return 0;
if (c <= 'F')
return (c - 'A') + 10;
// if (c > 'F')
return 0;
}