2020-01-19 12:43:48 +00:00
|
|
|
/**************************************************************************/
|
|
|
|
|
/*!
|
|
|
|
|
@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>
|
|
|
|
|
|
|
|
|
|
/**************************************************************************/
|
|
|
|
|
/*!
|
2020-01-29 15:18:41 +00:00
|
|
|
@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
|
2020-01-29 15:18:41 +00:00
|
|
|
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.
|
|
|
|
|
|
2020-01-19 12:43:48 +00:00
|
|
|
@param nmea Pointer to the NMEA string
|
2020-01-29 15:18:41 +00:00
|
|
|
@return True if successfully parsed, false if fails check or parsing
|
2020-01-19 12:43:48 +00:00
|
|
|
*/
|
|
|
|
|
/**************************************************************************/
|
2020-01-19 20:10:10 +00:00
|
|
|
bool Adafruit_GPS::parse(char *nmea) {
|
2020-01-19 12:43:48 +00:00
|
|
|
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
|
2020-01-29 15:18:41 +00:00
|
|
|
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
|
2020-01-19 12:43:48 +00:00
|
|
|
parseTime(p);
|
2020-01-29 15:18:41 +00:00
|
|
|
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);
|
2020-01-19 12:43:48 +00:00
|
|
|
p = strchr(p, ',') + 1;
|
|
|
|
|
p = strchr(p, ',') + 1;
|
2020-01-29 15:18:41 +00:00
|
|
|
// 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);
|
2020-01-19 12:43:48 +00:00
|
|
|
p = strchr(p, ',') + 1;
|
|
|
|
|
p = strchr(p, ',') + 1;
|
2020-01-29 15:18:41 +00:00
|
|
|
if (!isEmpty(p)) { // if it's a , (or a * at end of sentence) the value is
|
|
|
|
|
// not included
|
|
|
|
|
fixquality = atoi(p); // needs additional processing
|
2020-01-19 12:43:48 +00:00
|
|
|
if (fixquality > 0) {
|
|
|
|
|
fix = true;
|
|
|
|
|
lastFix = sentTime;
|
|
|
|
|
} else
|
|
|
|
|
fix = false;
|
|
|
|
|
}
|
2020-01-29 15:18:41 +00:00
|
|
|
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))
|
2020-01-19 12:43:48 +00:00
|
|
|
satellites = atoi(p);
|
|
|
|
|
p = strchr(p, ',') + 1;
|
2020-01-29 15:18:41 +00:00
|
|
|
if (!isEmpty(p))
|
|
|
|
|
newDataValue(NMEA_HDOP, HDOP = atof(p));
|
2020-01-19 12:43:48 +00:00
|
|
|
p = strchr(p, ',') + 1;
|
2020-01-29 15:18:41 +00:00
|
|
|
if (!isEmpty(p))
|
2020-01-19 12:43:48 +00:00
|
|
|
altitude = atof(p);
|
|
|
|
|
p = strchr(p, ',') + 1;
|
2020-01-29 15:18:41 +00:00
|
|
|
p = strchr(p, ',') + 1; // skip the units
|
|
|
|
|
if (!isEmpty(p))
|
|
|
|
|
geoidheight = atof(p); // skip the rest
|
2020-01-19 12:43:48 +00:00
|
|
|
|
2020-01-29 15:18:41 +00:00
|
|
|
} else if (!strcmp(thisSentence, "RMC")) { //*****************************RMC
|
|
|
|
|
// in Adafruit from Actisense NGW-1 from SH CP150C
|
2020-01-19 12:43:48 +00:00
|
|
|
parseTime(p);
|
|
|
|
|
p = strchr(p, ',') + 1;
|
2020-01-29 15:18:41 +00:00
|
|
|
parseFix(p);
|
2020-01-19 12:43:48 +00:00
|
|
|
p = strchr(p, ',') + 1;
|
2020-01-29 15:18:41 +00:00
|
|
|
// 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);
|
2020-01-19 12:43:48 +00:00
|
|
|
p = strchr(p, ',') + 1;
|
|
|
|
|
p = strchr(p, ',') + 1;
|
2020-01-29 15:18:41 +00:00
|
|
|
// 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);
|
2020-01-19 12:43:48 +00:00
|
|
|
p = strchr(p, ',') + 1;
|
|
|
|
|
p = strchr(p, ',') + 1;
|
2020-01-29 15:18:41 +00:00
|
|
|
if (!isEmpty(p))
|
|
|
|
|
newDataValue(NMEA_SOG, speed = atof(p));
|
2020-01-19 12:43:48 +00:00
|
|
|
p = strchr(p, ',') + 1;
|
2020-01-29 15:18:41 +00:00
|
|
|
if (!isEmpty(p))
|
|
|
|
|
newDataValue(NMEA_COG, angle = atof(p));
|
2020-01-19 12:43:48 +00:00
|
|
|
p = strchr(p, ',') + 1;
|
|
|
|
|
if (!isEmpty(p)) {
|
|
|
|
|
uint32_t fulldate = atof(p);
|
|
|
|
|
day = fulldate / 10000;
|
|
|
|
|
month = (fulldate % 10000) / 100;
|
|
|
|
|
year = (fulldate % 100);
|
|
|
|
|
lastDate = sentTime;
|
2020-01-29 15:18:41 +00:00
|
|
|
} // skip the rest
|
2020-01-19 12:43:48 +00:00
|
|
|
|
2020-01-29 15:18:41 +00:00
|
|
|
} 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);
|
2020-01-19 12:43:48 +00:00
|
|
|
p = strchr(p, ',') + 1;
|
|
|
|
|
p = strchr(p, ',') + 1;
|
2020-01-29 15:18:41 +00:00
|
|
|
// 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);
|
2020-01-19 12:43:48 +00:00
|
|
|
p = strchr(p, ',') + 1;
|
|
|
|
|
p = strchr(p, ',') + 1;
|
|
|
|
|
parseTime(p);
|
|
|
|
|
p = strchr(p, ',') + 1;
|
2020-01-29 15:18:41 +00:00
|
|
|
parseFix(p); // skip the rest
|
2020-01-19 12:43:48 +00:00
|
|
|
|
2020-01-29 15:18:41 +00:00
|
|
|
} else if (!strcmp(thisSentence, "GSA")) { //*****************************GSA
|
|
|
|
|
// in Adafruit from Actisense NGW-1
|
|
|
|
|
p = strchr(p, ',') + 1; // skip selection mode
|
|
|
|
|
if (!isEmpty(p))
|
2020-01-19 12:43:48 +00:00
|
|
|
fixquality_3d = atoi(p);
|
2020-01-29 15:18:41 +00:00
|
|
|
p = strchr(p, ',') + 1;
|
2020-01-19 12:43:48 +00:00
|
|
|
// skip 12 Satellite PDNs without interpreting them
|
|
|
|
|
for (int i = 0; i < 12; i++)
|
|
|
|
|
p = strchr(p, ',') + 1;
|
2020-01-29 15:18:41 +00:00
|
|
|
if (!isEmpty(p))
|
2020-01-19 12:43:48 +00:00
|
|
|
PDOP = atof(p);
|
2020-01-29 15:18:41 +00:00
|
|
|
p = strchr(p, ',') + 1;
|
2020-01-19 12:43:48 +00:00
|
|
|
// parse out HDOP, we also parse this from the GGA sentence. Chipset should
|
|
|
|
|
// report the same for both
|
2020-01-29 15:18:41 +00:00
|
|
|
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))
|
2020-02-23 23:52:46 +00:00
|
|
|
newDataValue(NMEA_DEPTH,
|
|
|
|
|
(nmea_float_t)atof(p) * 0.3048f + depthToTransducer);
|
2020-01-29 15:18:41 +00:00
|
|
|
p = strchr(p, ',') + 1;
|
|
|
|
|
p = strchr(p, ',') + 1;
|
|
|
|
|
if (!isEmpty(p))
|
2020-02-23 23:31:37 +00:00
|
|
|
newDataValue(NMEA_DEPTH, (nmea_float_t)atof(p) + depthToTransducer);
|
2020-01-29 15:18:41 +00:00
|
|
|
p = strchr(p, ',') + 1;
|
|
|
|
|
p = strchr(p, ',') + 1;
|
|
|
|
|
if (!isEmpty(p))
|
2020-02-23 23:52:46 +00:00
|
|
|
newDataValue(NMEA_DEPTH,
|
|
|
|
|
(nmea_float_t)atof(p) * 6 * 0.3048f + depthToTransducer);
|
2020-01-29 15:18:41 +00:00
|
|
|
|
|
|
|
|
} 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') {
|
2020-02-23 23:31:37 +00:00
|
|
|
T = (T - 32) / 1.8f;
|
2020-01-29 15:18:41 +00:00
|
|
|
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') {
|
2020-02-23 23:31:37 +00:00
|
|
|
T = (T - 32) / 1.8f;
|
2020-01-29 15:18:41 +00:00
|
|
|
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') {
|
2020-02-23 23:31:37 +00:00
|
|
|
T = (T - 32) / 1.8f;
|
2020-01-29 15:18:41 +00:00
|
|
|
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);
|
2020-01-19 12:43:48 +00:00
|
|
|
p = strchr(p, ',') + 1;
|
2020-01-29 15:18:41 +00:00
|
|
|
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') {
|
2020-02-23 23:31:37 +00:00
|
|
|
spd /= 1.6f;
|
2020-01-29 15:18:41 +00:00
|
|
|
units = 'M';
|
|
|
|
|
}
|
|
|
|
|
if (units == 'M') {
|
2020-02-23 23:31:37 +00:00
|
|
|
spd *= 5280.0f / 6000.0f;
|
2020-01-29 15:18:41 +00:00
|
|
|
units = 'N';
|
|
|
|
|
}
|
2020-02-23 23:31:37 +00:00
|
|
|
if (ang > 180.0f)
|
|
|
|
|
ang -= 360.0f;
|
2020-01-29 15:18:41 +00:00
|
|
|
if (ref == 'R') {
|
2020-02-23 23:31:37 +00:00
|
|
|
if (ang < 1000.0f && stat == 'A')
|
2020-01-29 15:18:41 +00:00
|
|
|
newDataValue(NMEA_AWA, ang);
|
2020-02-23 23:31:37 +00:00
|
|
|
if (spd < 1000.0f && stat == 'A')
|
2020-01-29 15:18:41 +00:00
|
|
|
newDataValue(NMEA_AWS, spd);
|
|
|
|
|
} else {
|
2020-02-23 23:31:37 +00:00
|
|
|
if (ang < 1000.0f && stat == 'A')
|
2020-01-29 15:18:41 +00:00
|
|
|
newDataValue(NMEA_TWA, ang);
|
2020-02-23 23:31:37 +00:00
|
|
|
if (spd < 1000.0f && stat == 'A')
|
2020-01-29 15:18:41 +00:00
|
|
|
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
|
2020-01-29 15:18:41 +00:00
|
|
|
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;
|
2020-02-23 23:31:37 +00:00
|
|
|
if (xte < 10000.0f && xteDir != 'X') {
|
2020-01-29 15:18:41 +00:00
|
|
|
if (xteDir == 'L')
|
2020-02-23 23:31:37 +00:00
|
|
|
xte *= -1.0f;
|
2020-01-29 15:18:41 +00:00
|
|
|
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;
|
2020-01-29 15:18:41 +00:00
|
|
|
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';
|
2020-01-29 15:18:41 +00:00
|
|
|
|
|
|
|
|
// parse out both latitude and direction for WayPoint, then go to next
|
|
|
|
|
// field, or fail
|
2020-01-19 12:43:48 +00:00
|
|
|
if (!isEmpty(p)) {
|
2020-01-29 15:18:41 +00:00
|
|
|
if (!parseCoord(p, &latitudeDegreesWP, &latitudeWP, &latitude_fixedWP,
|
|
|
|
|
&latWP))
|
|
|
|
|
return false;
|
|
|
|
|
else
|
|
|
|
|
newDataValue(NMEA_LATWP, latitudeDegreesWP);
|
2020-01-19 12:43:48 +00:00
|
|
|
}
|
|
|
|
|
p = strchr(p, ',') + 1;
|
2020-01-29 15:18:41 +00:00
|
|
|
p = strchr(p, ',') + 1;
|
|
|
|
|
// parse out both longitude and direction for WayPoint, then go to next
|
|
|
|
|
// field, or fail
|
2020-01-19 12:43:48 +00:00
|
|
|
if (!isEmpty(p)) {
|
2020-01-29 15:18:41 +00:00
|
|
|
if (!parseCoord(p, &longitudeDegreesWP, &longitudeWP, &longitude_fixedWP,
|
|
|
|
|
&lonWP))
|
|
|
|
|
return false;
|
|
|
|
|
else
|
|
|
|
|
newDataValue(NMEA_LONWP, longitudeDegreesWP);
|
2020-01-19 12:43:48 +00:00
|
|
|
}
|
2020-01-29 15:18:41 +00:00
|
|
|
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
|
2020-01-19 12:43:48 +00:00
|
|
|
|
2020-01-29 15:18:41 +00:00
|
|
|
} 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
|
2020-01-19 12:43:48 +00:00
|
|
|
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
|
2020-01-29 15:18:41 +00:00
|
|
|
|
|
|
|
|
} 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
|
2020-02-23 23:31:37 +00:00
|
|
|
if (vmg < 1000.0f)
|
2020-01-29 15:18:41 +00:00
|
|
|
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;
|
2020-02-23 23:31:37 +00:00
|
|
|
if (ang < 1000.0f)
|
2020-01-29 15:18:41 +00:00
|
|
|
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') {
|
2020-02-23 23:31:37 +00:00
|
|
|
ws *= 3.6f;
|
2020-01-29 15:18:41 +00:00
|
|
|
units = 'K';
|
|
|
|
|
} // convert m/s to km/h
|
|
|
|
|
if (units == 'K') {
|
2020-02-23 23:31:37 +00:00
|
|
|
ws /= 1.6f;
|
2020-01-29 15:18:41 +00:00
|
|
|
units = 'M';
|
|
|
|
|
} // convert km/h to miles / h
|
|
|
|
|
if (units == 'M') {
|
2020-02-23 23:31:37 +00:00
|
|
|
ws *= 5280.0f / 6000.0f;
|
2020-01-29 15:18:41 +00:00
|
|
|
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;
|
2020-02-23 23:31:37 +00:00
|
|
|
if (xte < 10000.0f && xteDir != 'X') {
|
2020-01-29 15:18:41 +00:00
|
|
|
if (xteDir == 'L')
|
2020-02-23 23:31:37 +00:00
|
|
|
xte *= -1.0f;
|
2020-01-29 15:18:41 +00:00
|
|
|
newDataValue(NMEA_XTE, xte);
|
|
|
|
|
} // skip units
|
|
|
|
|
|
|
|
|
|
} else if (!strcmp(thisSentence, "ZDA")) { //*****************************ZDA
|
|
|
|
|
// from Actisense NGW-1
|
|
|
|
|
return false;
|
2020-01-19 12:43:48 +00:00
|
|
|
}
|
|
|
|
|
#endif // NMEA_EXTENSIONS
|
|
|
|
|
|
2020-01-29 15:18:41 +00:00
|
|
|
else {
|
|
|
|
|
return false; // didn't find the required sentence definition
|
|
|
|
|
}
|
2020-01-19 12:43:48 +00:00
|
|
|
|
|
|
|
|
// Record the successful parsing of where the last data came from and when
|
|
|
|
|
strcpy(lastSource, thisSource);
|
|
|
|
|
strcpy(lastSentence, thisSentence);
|
|
|
|
|
lastUpdate = millis();
|
|
|
|
|
return true;
|
2020-01-19 20:10:10 +00:00
|
|
|
}
|
2020-01-29 15:18:41 +00:00
|
|
|
|
|
|
|
|
/**************************************************************************/
|
|
|
|
|
/*!
|
|
|
|
|
@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
|
|
|
|
|
*/
|
|
|
|
|
/**************************************************************************/
|
2020-02-23 23:31:37 +00:00
|
|
|
bool Adafruit_GPS::check(char *nmea) {
|
2020-01-29 15:18:41 +00:00
|
|
|
thisCheck = 0; // new check
|
2020-01-30 00:52:13 +00:00
|
|
|
*thisSentence = *thisSource = 0;
|
2020-01-29 15:18:41 +00:00
|
|
|
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;
|
2020-01-30 00:44:05 +00:00
|
|
|
return false; // known but not parsed
|
|
|
|
|
} else {
|
|
|
|
|
parseStr(thisSentence, p, NMEA_MAX_SENTENCE_ID);
|
|
|
|
|
return false; // unknown
|
2020-01-29 15:18:41 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
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
|
2020-01-29 15:18:41 +00:00
|
|
|
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
|
2020-01-29 15:18:41 +00:00
|
|
|
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)) {
|
2020-01-29 15:18:41 +00:00
|
|
|
// 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
|
2020-01-29 15:18:41 +00:00
|
|
|
|
|
|
|
|
// 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
|
|
|
|
2020-01-29 15:18:41 +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;
|
2020-01-29 15:18:41 +00:00
|
|
|
|
|
|
|
|
// 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);
|
2020-02-07 19:26:39 +00:00
|
|
|
char *dec = strchr(p, '.');
|
|
|
|
|
char *comstar = min(strchr(p, ','), strchr(p, '*'));
|
2020-02-08 01:20:30 +00:00
|
|
|
if (dec != NULL && comstar != NULL && dec < comstar)
|
2020-02-07 19:26:39 +00:00
|
|
|
milliseconds = atof(p) * 1000;
|
2020-02-08 01:20:30 +00:00
|
|
|
else
|
|
|
|
|
milliseconds = 0;
|
2020-01-29 15:18:41 +00:00
|
|
|
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
|
|
|
|
|
*/
|
|
|
|
|
/**************************************************************************/
|
2020-02-23 23:31:37 +00:00
|
|
|
bool Adafruit_GPS::parseFix(char *p) {
|
2020-01-29 15:18:41 +00:00
|
|
|
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;
|
|
|
|
|
}
|