New parse() with more sentences

move other parsing functions in NMEA_parse.cpp
fix parseTime to be insensitive to number of decimal places and return bool.
add isEmpty() checks where needed.
reorder some declarations.
This commit is contained in:
Rick Sellens 2020-01-29 10:18:41 -05:00
parent 4b6e181770
commit 3876f29fc1
3 changed files with 729 additions and 300 deletions

View File

@ -32,117 +32,6 @@
static bool strStartsWith(const char *str, const char *prefix);
/**************************************************************************/
/*!
@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
*/
/**************************************************************************/
bool Adafruit_GPS::check(char *nmea) {
thisCheck = 0; // new check
if (*nmea != '$')
return false; // doesn't start with $
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;
}
}
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 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 Is the field empty, or should we try conversion? Won't work
@ -151,32 +40,6 @@ char *Adafruit_GPS::parseStr(char *buff, char *p, int n) {
@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 part of an NMEA string for time
@param p Pointer to the location of the token in the NMEA string
*/
/**************************************************************************/
void Adafruit_GPS::parseTime(char *p) {
// get time
uint32_t time = atol(p);
hour = time / 10000;
minute = (time % 10000) / 100;
seconds = (time % 100);
p = strchr(p, '.') + 1;
milliseconds = atoi(p);
lastTime = sentTime;
}
/**************************************************************************/
/*!
@brief Parse a part of an NMEA string for latitude angle
@ -275,24 +138,6 @@ bool Adafruit_GPS::parseLonDir(char *p) {
return true;
}
/**************************************************************************/
/*!
@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
*/
/**************************************************************************/
bool Adafruit_GPS::parseFix(char *p) {
if (p[0] == 'A') {
fix = true;
lastFix = sentTime;
} else if (p[0] == 'V')
fix = false;
else
return false;
return true;
}
/**************************************************************************/
/*!
@brief Time in seconds since the last position fix was obtained. Will
@ -682,28 +527,6 @@ char *Adafruit_GPS::lastNMEA(void) {
return (char *)lastline;
}
/**************************************************************************/
/*!
@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;
}
/**************************************************************************/
/*!
@brief Wait for a specified sentence from the device

View File

@ -100,14 +100,10 @@ public:
void pause(bool b);
uint8_t parseHex(char c);
char read(void);
size_t write(uint8_t);
size_t available(void);
bool check(char *nmea);
bool parse(char *);
nmea_float_t secondsSinceFix();
nmea_float_t secondsSinceTime();
nmea_float_t secondsSinceDate();
@ -116,6 +112,12 @@ public:
bool wakeup(void);
bool standby(void);
// NMEA_parse.cpp
bool parse(char *);
bool check(char *nmea);
bool onList(char *nmea, const char **list);
uint8_t parseHex(char c);
// NMEA_build.cpp
#ifdef NMEA_EXTENSIONS
char *build(char *nmea, const char *thisSource, const char *thisSentence,
@ -234,17 +236,22 @@ public:
#endif // NMEA_EXTENSIONS
private:
const char *tokenOnList(char *token, const char **list);
char *parseStr(char *buff, char *p, int n);
bool isEmpty(char *pStart);
void parseTime(char *);
void parseLat(char *);
bool parseLatDir(char *);
void parseLon(char *);
bool parseLonDir(char *);
// NMEA_data.cpp
void data_init();
// NMEA_parse.cpp
const char *tokenOnList(char *token, const char **list);
bool parseCoord(char *p, nmea_float_t *angleDegrees = NULL,
nmea_float_t *angle = NULL, int32_t *angle_fixed = NULL,
char *dir = NULL);
char *parseStr(char *buff, char *p, int n);
bool parseTime(char *);
bool parseFix(char *);
bool isEmpty(char *pStart);
// used by check() for validity tests, room for future expansion
const char *sources[6] = {"II", "WI", "GP", "GN",
"P", "ZZZ"}; ///< valid source ids

View File

@ -32,112 +32,102 @@
/**************************************************************************/
/*!
@brief Parse a NMEA string
@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.
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 we parsed it, false if it has an invalid checksum or invalid
data
@return True if successfully parsed, false if fails check or parsing
*/
/**************************************************************************/
bool Adafruit_GPS::parse(char *nmea) {
// do checksum check
if (!check(nmea))
return false;
// passed the check, so there's a valid source in thisSource and a valid
// sentence in thisSentence
// look for a few common sentences
char *p = nmea; // Pointer to move through the sentence -- good parsers are
// non-destructive
p = strchr(p, ',') +
1; // Skip to the character after the next comma, then check sentence.
p = strchr(p, ',') + 1; // Skip to char after the next comma, then check.
if (!strcmp(thisSentence, "GGA")) {
// found GGA
// get time
// 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);
// parse out latitude
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;
parseLat(p);
p = strchr(p, ',') + 1;
if (!parseLatDir(p))
return false;
// parse out longitude
// 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;
parseLon(p);
p = strchr(p, ',') + 1;
if (!parseLonDir(p))
return false;
p = strchr(p, ',') + 1;
if (!isEmpty(p)) {
fixquality = atoi(p);
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;
if (!isEmpty(p)) {
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)) {
HDOP = atof(p);
}
if (!isEmpty(p))
newDataValue(NMEA_HDOP, HDOP = atof(p));
p = strchr(p, ',') + 1;
if (!isEmpty(p)) {
if (!isEmpty(p))
altitude = atof(p);
}
p = strchr(p, ',') + 1;
p = strchr(p, ',') + 1;
if (!isEmpty(p)) {
geoidheight = atof(p);
}
}
p = strchr(p, ',') + 1; // skip the units
if (!isEmpty(p))
geoidheight = atof(p); // skip the rest
else if (!strcmp(thisSentence, "RMC")) {
// found RMC
// get time
} else if (!strcmp(thisSentence, "RMC")) { //*****************************RMC
// in Adafruit from Actisense NGW-1 from SH CP150C
parseTime(p);
// fix or no fix
p = strchr(p, ',') + 1;
if (!parseFix(p))
return false;
// parse out latitude
parseFix(p);
p = strchr(p, ',') + 1;
parseLat(p);
// 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;
if (!parseLatDir(p))
return false;
// parse out longitude
p = strchr(p, ',') + 1;
parseLon(p);
// 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;
if (!parseLonDir(p))
return false;
// speed
p = strchr(p, ',') + 1;
if (!isEmpty(p)) {
speed = atof(p);
}
// angle
if (!isEmpty(p))
newDataValue(NMEA_SOG, speed = atof(p));
p = strchr(p, ',') + 1;
if (!isEmpty(p)) {
angle = atof(p);
}
if (!isEmpty(p))
newDataValue(NMEA_COG, angle = atof(p));
p = strchr(p, ',') + 1;
if (!isEmpty(p)) {
uint32_t fulldate = atof(p);
@ -145,66 +135,264 @@ bool Adafruit_GPS::parse(char *nmea) {
month = (fulldate % 10000) / 100;
year = (fulldate % 100);
lastDate = sentTime;
}
}
} // skip the rest
else if (!strcmp(thisSentence, "GLL")) {
// found GLL
// parse out latitude
parseLat(p);
} 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;
if (!parseLatDir(p))
return false;
// parse out longitude
p = strchr(p, ',') + 1;
parseLon(p);
// 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;
if (!parseLonDir(p))
return false;
// get time
p = strchr(p, ',') + 1;
parseTime(p);
// fix or no fix
p = strchr(p, ',') + 1;
if (!parseFix(p))
return false;
}
parseFix(p); // skip the rest
else if (!strcmp(thisSentence, "GSA")) {
// found GSA
// parse out Auto selection, but ignore them
// parse out 3d fixquality
p = strchr(p, ',') + 1;
if (!isEmpty(p)) {
} 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;
// parse out PDOP
p = strchr(p, ',') + 1;
if (!isEmpty(p)) {
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)) {
HDOP = atof(p);
}
// parse out VDOP
p = strchr(p, ',') + 1;
if (!isEmpty(p)) {
VDOP = atof(p);
}
}
if (!isEmpty(p))
VDOP = atof(p); // last before checksum
}
#ifdef NMEA_EXTENSIONS // Sentences not required for basic GPS functionality
else if (!strcmp(thisSentence, "TXT")) { //*******************************TXT
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
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;
nmea_float_t latitudeWP =
0; // All the same position data for the next way point
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;
char latWP = 'N';
char lonWP = 'W';
// 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;
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;
@ -216,12 +404,128 @@ bool Adafruit_GPS::parse(char *nmea) {
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
// we dont parse the remaining, yet!
else
return false;
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);
@ -229,3 +533,298 @@ bool Adafruit_GPS::parse(char *nmea) {
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
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;
}
}
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
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,
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;
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
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;
// reject angles that are out of range
if (nsew == 'N' || nsew == 'S')
if(abs(deg) > 90) return false;
if(abs(deg) > 180) 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);
p = strchr(p, '.');
milliseconds = atof(p) * 1000;
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;
}