Merge pull request #113 from sellensr/master

Add data values and history to NMEA_EXTENSIONS, plus more sentences
This commit is contained in:
Matt Goodrich 2020-01-29 16:57:10 -05:00 committed by GitHub
commit a57c52f242
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 2375 additions and 562 deletions

View File

@ -34,9 +34,10 @@ Adafruit_GPS GPS(&GPSSerial);
#ifdef NMEA_EXTENSIONS #ifdef NMEA_EXTENSIONS
// Create another GPS object to hold the state of the boat, with no // Create another GPS object to hold the state of the boat, with no
// communications, so don't call Boat.begin() in setup. We will build some fake // communications, so you don't need to call Boat.begin() in setup.
// sentences from the Boat data to feed to GPS for testing. // We will build some fake sentences from the Boat data to feed to
Adafruit_GPS Boat(&GPSSerial); // GPS for testing.
Adafruit_GPS Boat;
#endif #endif
// Set GPSECHO to 'false' to turn off echoing the GPS data to the Serial console // Set GPSECHO to 'false' to turn off echoing the GPS data to the Serial console

View File

@ -0,0 +1,143 @@
/**************************************************************************/
/*!
@file NMEA_EXTENSIONS.ino
@section intro Introduction
An Arduino sketch for testing the NMEA_EXTENSIONS to the library. Does
not require any GPS hardware. Boat is a data only object we can use to
represent the actual data and build sentences from. GPS is a data only
object that parses the sentences and saves the results, the same way you
would with a communicating GPS object
Only some of the data values will have added history. Note that history
is stored as integers, scaled and offset from the float values to save
memory. The AWA (Apparent Wind Angle) is recorded as three components,
so that sin and cos parts can be accurately time averaged. onList() allows
testing sentences against a list to see if they should be passed on to
another listener, allowing your sketch to act as an NMEA multiplexer.
Although it will just barely compile for an UNO with the NMEA_EXTENSIONS,
defining two GPS objects pushes the limits of the UNO data space and
should probably be avoided.
@section author Author
Written by Rick Sellens.
@section license License
CCBY license
*/
/**************************************************************************/
#include "Adafruit_GPS.h"
Adafruit_GPS GPS; // The results obtained from the instruments -- no comms
Adafruit_GPS Boat; // The state of the boat used to create some simulated sentences
void setup() {
Serial.begin(115200);
while (!Serial && millis() < 10000); // Wait for monitor to be ready.
Serial.print("\n\nNMEA_EXTENSIONS Example v 0.1.1\n\n");
#ifdef NMEA_EXTENSIONS
addHistory(&GPS);
#else
Serial.print("NMEA_EXTENSIONS not #defined, so there will be no action.\n");
#endif
}
char latestBoat[200] = "";
const char *senList[] = {"GGA", "GLL", "DBT", "HDM", "MWV", "ZZ"}; // sentence list
const char *passList[] = {"GGA", "DBT", "ZZ"}; // short list
void loop() {
static unsigned long lastPrint = 0;
updateBoat();
// stop keeping AWA history after 30 seconds, just as a demonstration
#ifdef NMEA_EXTENSIONS
if(millis() > 30000) GPS.removeHistory(NMEA_AWA);
#endif
if (millis() - lastPrint > 300 || lastPrint == 0) {
lastPrint = millis();
#ifdef NMEA_EXTENSIONS
Serial.print("\nSentences built from Boat and parsed by GPS ");
Serial.print("(Only a few get passed on to the ST network.):\n\n");
for (int i = 0;strncmp(senList[i],"ZZ",2);i++){
if (GPS.parse(Boat.build(latestBoat, "II", senList[i]))){
if (GPS.onList(latestBoat,passList))
Serial.print("Pass to ST: ");
else Serial.print(" No Pass: ");
Serial.print(latestBoat);
} else {
Serial.print("Couldn't build and parse a ");
Serial.print(senList[i]);
Serial.print(" sentence, maybe because sprintf() doesn't work with %f.");
}
}
Serial.print("\nSome of the resulting data stored in GPS:\n\n");
GPS.showDataValue(NMEA_LAT);
GPS.showDataValue(NMEA_LON);
GPS.showDataValue(NMEA_AWA, 20); // show more history values, if history on
GPS.showDataValue(NMEA_AWA_SIN);
GPS.showDataValue(NMEA_AWA_COS);
GPS.showDataValue(NMEA_AWS);
GPS.showDataValue(NMEA_HDG);
GPS.showDataValue(NMEA_DEPTH);
Serial.print("\nThe AWA is: ");
Serial.print(GPS.get(NMEA_AWA));
Serial.print(" while the smoothed value is: ");
Serial.println(GPS.getSmoothed(NMEA_AWA));
#endif // NMEA_Extensions
}
}
void updateBoat() { // Fill up the boat values with
// some test data to use in build()
nmea_float_t t = millis() / 1000.;
nmea_float_t theta = t / 100.; // slow
nmea_float_t gamma = theta * 10; // faster
// add some data to the old Adafruit_GPS variables
Boat.latitude = 4400 + sin(theta) * 60;
Boat.lat = 'N';
Boat.longitude = 7600 + cos(theta) * 60;
Boat.lon = 'W';
Boat.fixquality = 2;
Boat.speed = 3 + sin(gamma);
Boat.hour = abs(cos(theta)) * 24;
Boat.minute = 30 + sin(theta / 2) * 30;
Boat.seconds = 30 + sin(gamma) * 30;
Boat.milliseconds = 500 + sin(gamma) * 500;
Boat.year = 1 + abs(sin(theta)) * 25;
Boat.month = 1 + abs(sin(gamma)) * 11;
Boat.day = 1 + abs(sin(gamma)) * 26;
Boat.satellites = abs(cos(gamma)) * 10;
#ifdef NMEA_EXTENSIONS
// add some data to the new NMEA data values
Boat.newDataValue(NMEA_AWS, 10 + cos(theta));
Boat.newDataValue(NMEA_AWA, 180 * sin(gamma));
Boat.newDataValue(NMEA_VTW, Boat.speed + cos(gamma) / 3);
Boat.newDataValue(NMEA_DEPTH, 10 + cos(gamma) * 5);
Boat.newDataValue(NMEA_HDG, 180 * sin(gamma) + 180);
Boat.newDataValue(NMEA_HDT, 180 * cos(gamma) + 180);
Boat.newDataValue(NMEA_VMG, sin(gamma) * 3);
Boat.newDataValue(NMEA_VMGWP, cos(gamma) * 5);
#endif // NMEA_EXTENSIONS
}
#ifdef NMEA_EXTENSIONS
void addHistory(Adafruit_GPS *nmea) {
// Record integer history for HDOP, scaled by 10.0, offset by 0.0,
// every 15 seconds for the most recent 20 values.
nmea->initHistory(NMEA_HDOP, 10.0, 0.0, 15, 20);
nmea->initHistory(NMEA_COG, 10.0, 0.0, 1);
nmea->initHistory(NMEA_AWA, 10.0, 0.0, 1);
nmea->initHistory(NMEA_HDG, 10.0, 0.0, 3);
// Record pressure every 10 minutes, in Pa relative to 1 bar
nmea->initHistory(NMEA_BAROMETER, 1.0, -100000.0, 600);
nmea->initHistory(NMEA_DEPTH, 10.0, 0.0, 3);
}
#endif // NMEA_EXTENSIONS

216
keywords.txt Normal file
View File

@ -0,0 +1,216 @@
#######################################
# Syntax Coloring Map For RWS_NMEA
#######################################
# https://github.com/arduino/Arduino/wiki/Arduino-IDE-1.5:-Library-specification
#######################################
# Datatypes (KEYWORD1)
#######################################
Adafruit_GPS KEYWORD1
nmea_float_t KEYWORD1
nmea_history_t KEYWORD1
nmea_datavalue_t KEYWORD1
nmea_index_t KEYWORD1
nmea_check_t KEYWORD1
nmea_value_type_t KEYWORD1
#######################################
# Methods and Functions (KEYWORD2)
#######################################
lastNMEA KEYWORD2
newNMEAreceived KEYWORD2
common_init KEYWORD2
sendCommand KEYWORD2
pause KEYWORD2
parse KEYWORD2
parseHex KEYWORD2
check KEYWORD2
isEmpty KEYWORD2
addChecksum KEYWORD2
boatAngle KEYWORD2
compassAngle KEYWORD2
secondsSinceFix KEYWORD2
secondsSinceTime KEYWORD2
secondsSinceDate KEYWORD2
resetSentTime KEYWORD2
wakeup KEYWORD2
standby KEYWORD2
onList KEYWORD2
parseStr KEYWORD2
parseCoord KEYWORD2
newDataValue KEYWORD2
initDataValue KEYWORD2
initHistory KEYWORD2
removeHistory KEYWORD2
showDataValue KEYWORD2
get KEYWORD2
getSmoothed KEYWORD2
isCompoundAngle KEYWORD2
waitForSentence KEYWORD2
LOCUS_StartLogger KEYWORD2
LOCUS_StopLogger KEYWORD2
LOCUS_ReadStatus KEYWORD2
build KEYWORD2
#######################################
# Instances (KEYWORD2)
#######################################
seconds KEYWORD2
milliseconds KEYWORD2
latitude KEYWORD2
longitude KEYWORD2
latitude_fixed KEYWORD2
longitude_fixed KEYWORD2
latitudeDegrees KEYWORD2
longitudeDegrees KEYWORD2
lat KEYWORD2
lon KEYWORD2
geoidheight KEYWORD2
altitude KEYWORD2
speed KEYWORD2
angle KEYWORD2
magvariation KEYWORD2
HDOP KEYWORD2
VDOP KEYWORD2
PDOP KEYWORD2
mag KEYWORD2
fix KEYWORD2
fixquality KEYWORD2
fixquality_3d KEYWORD2
satellites KEYWORD2
LOCUS_serial KEYWORD2
LOCUS_records KEYWORD2
LOCUS_type KEYWORD2
LOCUS_mode KEYWORD2
LOCUS_config KEYWORD2
LOCUS_interval KEYWORD2
LOCUS_distance KEYWORD2
LOCUS_speed KEYWORD2
LOCUS_status KEYWORD2
LOCUS_percent KEYWORD2
val KEYWORD2
depthToKeel KEYWORD2
depthToTransducer KEYWORD2
toid KEYWORD2
fromid KEYWORD2
txtTXT KEYWORD2
txtTot KEYWORD2
txtID KEYWORD2
txtN KEYWORD2
thisCheck KEYWORD2
thisSource KEYWORD2
thisSentence KEYWORD2
lastSource KEYWORD2
lastSentence KEYWORD2
data KEYWORD2
lastHistory KEYWORD2
historyInterval KEYWORD2
scale KEYWORD2
offset KEYWORD2
latest KEYWORD2
smoothed KEYWORD2
lastUpdate KEYWORD2
response KEYWORD2
type KEYWORD2
ockam KEYWORD2
hist KEYWORD2
label KEYWORD2
unit KEYWORD2
fmt KEYWORD2
#######################################
# Constants (LITERAL1)
#######################################
NMEA_EXTENSIONS LITERAL1
NMEA_EXTRAS LITERAL1
USE_SW_SERIAL LITERAL1
GPS_DEFAULT_I2C_ADDR LITERAL1
GPS_MAX_I2C_TRANSFER LITERAL1
GPS_MAX_SPI_TRANSFER LITERAL1
MAXLINELENGTH LITERAL1
NMEA_BAD LITERAL1
NMEA_HAS_DOLLAR LITERAL1
NMEA_HAS_CHECKSUM LITERAL1
NMEA_HAS_NAME LITERAL1
NMEA_HAS_SOURCE LITERAL1
NMEA_HAS_SENTENCE LITERAL1
NMEA_HAS_SENTENCE_P LITERAL1
NMEA_SIMPLE_FLOAT LITERAL1
NMEA_COMPASS_ANGLE LITERAL1
NMEA_BOAT_ANGLE LITERAL1
NMEA_COMPASS_ANGLE_SIN LITERAL1
NMEA_BOAT_ANGLE_SIN LITERAL1
NMEA_DDMM LITERAL1
NMEA_HHMMSS LITERAL1
NMEA_MAX_WP_ID LITERAL1
NMEA_MAX_SENTENCE_ID LITERAL1
NMEA_MAX_SOURCE_ID LITERAL1
NMEA_N_HIST LITERAL1
DEG_RAD LITERAL1
NMEA_HDOP LITERAL1
NMEA_LAT LITERAL1
NMEA_LON LITERAL1
NMEA_LATWP LITERAL1
NMEA_LONWP LITERAL1
NMEA_SOG LITERAL1
NMEA_COG LITERAL1
NMEA_COG_SIN LITERAL1
NMEA_COG_COS LITERAL1
NMEA_COGWP LITERAL1
NMEA_XTE LITERAL1
NMEA_DISTWP LITERAL1
NMEA_AWA LITERAL1
NMEA_AWA_SIN LITERAL1
NMEA_AWA_COS LITERAL1
NMEA_AWS LITERAL1
NMEA_TWA LITERAL1
NMEA_TWA_SIN LITERAL1
NMEA_TWA_COS LITERAL1
NMEA_TWD LITERAL1
NMEA_TWD_SIN LITERAL1
NMEA_TWD_COS LITERAL1
NMEA_TWS LITERAL1
NMEA_VMG LITERAL1
NMEA_VMGWP LITERAL1
NMEA_HEEL LITERAL1
NMEA_PITCH LITERAL1
NMEA_HDG LITERAL1
NMEA_HDG_SIN LITERAL1
NMEA_HDG_COS LITERAL1
NMEA_HDT LITERAL1
NMEA_HDT_SIN LITERAL1
NMEA_HDT_COS LITERAL1
NMEA_VTW LITERAL1
NMEA_LOG LITERAL1
NMEA_LOGR LITERAL1
NMEA_DEPTH LITERAL1
NMEA_RPM_M1 LITERAL1
NMEA_TEMPERATURE_M1 LITERAL1
NMEA_PRESSURE_M1 LITERAL1
NMEA_VOLTAGE_M1 LITERAL1
NMEA_CURRENT_M1 LITERAL1
NMEA_RPM_M2 LITERAL1
NMEA_TEMPERATURE_M2 LITERAL1
NMEA_PRESSURE_M2 LITERAL1
NMEA_VOLTAGE_M2 LITERAL1
NMEA_CURRENT_M2 LITERAL1
NMEA_TEMPERATURE_AIR LITERAL1
NMEA_TEMPERATURE_WATER LITERAL1
NMEA_HUMIDITY LITERAL1
NMEA_BAROMETER LITERAL1
NMEA_USR_00 LITERAL1
NMEA_USR_01 LITERAL1
NMEA_USR_02 LITERAL1
NMEA_USR_03 LITERAL1
NMEA_USR_04 LITERAL1
NMEA_USR_05 LITERAL1
NMEA_USR_06 LITERAL1
NMEA_USR_07 LITERAL1
NMEA_USR_08 LITERAL1
NMEA_USR_09 LITERAL1
NMEA_USR_10 LITERAL1
NMEA_USR_11 LITERAL1
NMEA_USR_12 LITERAL1
NMEA_MAX_INDEX LITERAL1

View File

@ -32,117 +32,6 @@
static bool strStartsWith(const char *str, const char *prefix); 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 @brief Is the field empty, or should we try conversion? Won't work
@ -151,77 +40,30 @@ char *Adafruit_GPS::parseStr(char *buff, char *p, int n) {
@param pStart Pointer to the location of the token in the NMEA string @param pStart Pointer to the location of the token in the NMEA string
@return true if empty field, false if something there @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 Add *CS where CS is the two character hex checksum for all but
the first character in the string. The checksum is the result of an
exclusive or of all the characters in the string. Also useful if you
are creating new PMTK strings for controlling a GPS module and need a
checksum added.
@param buff Pointer to the string, which must be long enough
@return none
*/
/**************************************************************************/
void Adafruit_GPS::addChecksum(char *buff) {
char cs = 0;
int i = 1;
while (buff[i]) {
cs ^= buff[i];
i++;
}
sprintf(buff, "%s*%02X", buff, cs);
}
/**************************************************************************/
/*!
@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 @brief Parse a part of an NMEA string for latitude angle
@param p Pointer to the location of the token in the NMEA string @param p Pointer to the location of the token in the NMEA string
*/ */
/**************************************************************************/ /**************************************************************************/
void Adafruit_GPS::parseLat(char *p) { // void Adafruit_GPS::parseLat(char *p) {
char degreebuff[10]; // char degreebuff[10];
if (!isEmpty(p)) { // if (!isEmpty(p)) {
strncpy(degreebuff, p, 2); // strncpy(degreebuff, p, 2);
p += 2; // p += 2;
degreebuff[2] = '\0'; // degreebuff[2] = '\0';
long degree = atol(degreebuff) * 10000000; // long degree = atol(degreebuff) * 10000000;
strncpy(degreebuff, p, 2); // minutes // strncpy(degreebuff, p, 2); // minutes
p += 3; // skip decimal point // p += 3; // skip decimal point
strncpy(degreebuff + 2, p, 4); // strncpy(degreebuff + 2, p, 4);
degreebuff[6] = '\0'; // degreebuff[6] = '\0';
long minutes = 50 * atol(degreebuff) / 3; // long minutes = 50 * atol(degreebuff) / 3;
latitude_fixed = degree + minutes; // latitude_fixed = degree + minutes;
latitude = degree / 100000 + minutes * 0.000006F; // latitude = degree / 100000 + minutes * 0.000006F;
latitudeDegrees = (latitude - 100 * int(latitude / 100)) / 60.0f; // latitudeDegrees = (latitude - 100 * int(latitude / 100)) / 60.0f;
latitudeDegrees += int(latitude / 100); // latitudeDegrees += int(latitude / 100);
} // }
} // }
/**************************************************************************/ /**************************************************************************/
/*! /*!
@ -230,20 +72,20 @@ void Adafruit_GPS::parseLat(char *p) {
@return True if we parsed it, false if it has invalid data @return True if we parsed it, false if it has invalid data
*/ */
/**************************************************************************/ /**************************************************************************/
bool Adafruit_GPS::parseLatDir(char *p) { // bool Adafruit_GPS::parseLatDir(char *p) {
if (p[0] == 'S') { // if (p[0] == 'S') {
lat = 'S'; // lat = 'S';
latitudeDegrees *= -1.0f; // latitudeDegrees *= -1.0f;
latitude_fixed *= -1; // latitude_fixed *= -1;
} else if (p[0] == 'N') { // } else if (p[0] == 'N') {
lat = 'N'; // lat = 'N';
} else if (p[0] == ',') { // } else if (p[0] == ',') {
lat = 0; // lat = 0;
} else { // } else {
return false; // return false;
} // }
return true; // return true;
} // }
/**************************************************************************/ /**************************************************************************/
/*! /*!
@ -251,26 +93,26 @@ bool Adafruit_GPS::parseLatDir(char *p) {
@param p Pointer to the location of the token in the NMEA string @param p Pointer to the location of the token in the NMEA string
*/ */
/**************************************************************************/ /**************************************************************************/
void Adafruit_GPS::parseLon(char *p) { // void Adafruit_GPS::parseLon(char *p) {
int32_t degree; // int32_t degree;
long minutes; // long minutes;
char degreebuff[10]; // char degreebuff[10];
if (!isEmpty(p)) { // if (!isEmpty(p)) {
strncpy(degreebuff, p, 3); // strncpy(degreebuff, p, 3);
p += 3; // p += 3;
degreebuff[3] = '\0'; // degreebuff[3] = '\0';
degree = atol(degreebuff) * 10000000; // degree = atol(degreebuff) * 10000000;
strncpy(degreebuff, p, 2); // minutes // strncpy(degreebuff, p, 2); // minutes
p += 3; // skip decimal point // p += 3; // skip decimal point
strncpy(degreebuff + 2, p, 4); // strncpy(degreebuff + 2, p, 4);
degreebuff[6] = '\0'; // degreebuff[6] = '\0';
minutes = 50 * atol(degreebuff) / 3; // minutes = 50 * atol(degreebuff) / 3;
longitude_fixed = degree + minutes; // longitude_fixed = degree + minutes;
longitude = degree / 100000 + minutes * 0.000006F; // longitude = degree / 100000 + minutes * 0.000006F;
longitudeDegrees = (longitude - 100 * int(longitude / 100)) / 60.0f; // longitudeDegrees = (longitude - 100 * int(longitude / 100)) / 60.0f;
longitudeDegrees += int(longitude / 100); // longitudeDegrees += int(longitude / 100);
} // }
} // }
/**************************************************************************/ /**************************************************************************/
/*! /*!
@ -279,81 +121,163 @@ void Adafruit_GPS::parseLon(char *p) {
@return True if we parsed it, false if it has invalid data @return True if we parsed it, false if it has invalid data
*/ */
/**************************************************************************/ /**************************************************************************/
bool Adafruit_GPS::parseLonDir(char *p) { // bool Adafruit_GPS::parseLonDir(char *p) {
if (!isEmpty(p)) { // if (!isEmpty(p)) {
if (p[0] == 'W') { // if (p[0] == 'W') {
lon = 'W'; // lon = 'W';
longitudeDegrees *= -1.0f; // longitudeDegrees *= -1.0f;
longitude_fixed *= -1; // longitude_fixed *= -1;
} else if (p[0] == 'E') { // } else if (p[0] == 'E') {
lon = 'E'; // lon = 'E';
} else if (p[0] == ',') { // } else if (p[0] == ',') {
lon = 0; // lon = 0;
// } else {
// return false;
// }
// }
// return true;
// }
/**************************************************************************/
/*!
@brief Start the HW or SW serial port
@param baud_or_i2caddr Baud rate if using serial, I2C address if using I2C
@returns True on successful hardware init, False on failure
*/
/**************************************************************************/
bool Adafruit_GPS::begin(uint32_t baud_or_i2caddr) {
#if (defined(__AVR__) || defined(ESP8266)) && defined(USE_SW_SERIAL)
if (gpsSwSerial) {
gpsSwSerial->begin(baud_or_i2caddr);
}
#endif
if (gpsHwSerial) {
gpsHwSerial->begin(baud_or_i2caddr);
}
if (gpsI2C) {
gpsI2C->begin();
if (baud_or_i2caddr > 0x7F) {
_i2caddr = GPS_DEFAULT_I2C_ADDR;
} else { } else {
return false; _i2caddr = baud_or_i2caddr;
}
// A basic scanner, see if it ACK's
gpsI2C->beginTransmission(_i2caddr);
return (gpsI2C->endTransmission() == 0);
}
if (gpsSPI) {
gpsSPI->begin();
gpsSPI_settings = SPISettings(baud_or_i2caddr, MSBFIRST, SPI_MODE0);
if (gpsSPI_cs >= 0) {
pinMode(gpsSPI_cs, OUTPUT);
digitalWrite(gpsSPI_cs, HIGH);
} }
} }
delay(10);
return true; return true;
} }
/**************************************************************************/ /**************************************************************************/
/*! /*!
@brief Parse a part of an NMEA string for whether there is a fix @brief Constructor when using SoftwareSerial
@param p Pointer to the location of the token in the NMEA string @param ser Pointer to SoftwareSerial device
@return True if we parsed it, false if it has invalid data
*/ */
/**************************************************************************/ /**************************************************************************/
bool Adafruit_GPS::parseFix(char *p) { #if (defined(__AVR__) || defined(ESP8266)) && defined(USE_SW_SERIAL)
if (p[0] == 'A') { Adafruit_GPS::Adafruit_GPS(SoftwareSerial *ser) {
fix = true; common_init(); // Set everything to common state, then...
lastFix = sentTime; gpsSwSerial = ser; // ...override gpsSwSerial with value passed.
} else if (p[0] == 'V') }
fix = false; #endif
else
return false; /**************************************************************************/
return true; /*!
@brief Constructor when using HardwareSerial
@param ser Pointer to a HardwareSerial object
*/
/**************************************************************************/
Adafruit_GPS::Adafruit_GPS(HardwareSerial *ser) {
common_init(); // Set everything to common state, then...
gpsHwSerial = ser; // ...override gpsHwSerial with value passed.
} }
/**************************************************************************/ /**************************************************************************/
/*! /*!
@brief Time in seconds since the last position fix was obtained. Will @brief Constructor when using I2C
fail by rolling over to zero after one millis() cycle, about 6-1/2 weeks. @param theWire Pointer to an I2C TwoWire object
@return nmea_float_t value in seconds since last fix.
*/ */
/**************************************************************************/ /**************************************************************************/
nmea_float_t Adafruit_GPS::secondsSinceFix() { Adafruit_GPS::Adafruit_GPS(TwoWire *theWire) {
return (millis() - lastFix) / 1000.; common_init(); // Set everything to common state, then...
gpsI2C = theWire; // ...override gpsI2C
} }
/**************************************************************************/ /**************************************************************************/
/*! /*!
@brief Time in seconds since the last GPS time was obtained. Will fail @brief Constructor when using SPI
by rolling over to zero after one millis() cycle, about 6-1/2 weeks. @param theSPI Pointer to an SPI device object
@return nmea_float_t value in seconds since last GPS time. @param cspin The pin connected to the GPS CS, can be -1 if unused
*/ */
/**************************************************************************/ /**************************************************************************/
nmea_float_t Adafruit_GPS::secondsSinceTime() { Adafruit_GPS::Adafruit_GPS(SPIClass *theSPI, int8_t cspin) {
return (millis() - lastTime) / 1000.; common_init(); // Set everything to common state, then...
gpsSPI = theSPI; // ...override gpsSPI
gpsSPI_cs = cspin;
} }
/**************************************************************************/ /**************************************************************************/
/*! /*!
@brief Time in seconds since the last GPS date was obtained. Will fail @brief Constructor when there are no communications attached
by rolling over to zero after one millis() cycle, about 6-1/2 weeks.
@return nmea_float_t value in seconds since last GPS date.
*/ */
/**************************************************************************/ /**************************************************************************/
nmea_float_t Adafruit_GPS::secondsSinceDate() { Adafruit_GPS::Adafruit_GPS() {
return (millis() - lastDate) / 1000.; common_init(); // Set everything to common state, then...
noComms = true;
} }
/**************************************************************************/ /**************************************************************************/
/*! /*!
@brief Fakes time of receipt of a sentence. Use between build() and parse() @brief Initialization code used by all constructor types
to make the timing look like the sentence arrived from the GPS.
*/ */
/**************************************************************************/ /**************************************************************************/
void Adafruit_GPS::resetSentTime() { sentTime = millis(); } void Adafruit_GPS::common_init(void) {
#if (defined(__AVR__) || defined(ESP8266)) && defined(USE_SW_SERIAL)
gpsSwSerial = NULL; // Set both to NULL, then override correct
#endif
gpsHwSerial = NULL; // port pointer in corresponding constructor
gpsI2C = NULL;
gpsSPI = NULL;
recvdflag = false;
paused = false;
lineidx = 0;
currentline = line1;
lastline = line2;
hour = minute = seconds = year = month = day = fixquality = fixquality_3d =
satellites = 0; // uint8_t
lat = lon = mag = 0; // char
fix = false; // bool
milliseconds = 0; // uint16_t
latitude = longitude = geoidheight = altitude = speed = angle = magvariation =
HDOP = VDOP = PDOP = 0.0; // nmea_float_t
#ifdef NMEA_EXTENSIONS
data_init();
#endif
}
/**************************************************************************/
/*!
@brief Destroy the object.
@return none
*/
/**************************************************************************/
Adafruit_GPS::~Adafruit_GPS() {
#ifdef NMEA_EXTENSIONS
for (int i = 0; i < (int)NMEA_MAX_INDEX; i++)
removeHistory((nmea_index_t)i); // to free any history mallocs
#endif
}
/**************************************************************************/ /**************************************************************************/
/*! /*!
@ -434,7 +358,7 @@ char Adafruit_GPS::read(void) {
uint32_t tStart = millis(); // as close as we can get to time char was sent uint32_t tStart = millis(); // as close as we can get to time char was sent
char c = 0; char c = 0;
if (paused) if (paused || noComms)
return c; return c;
#if (defined(__AVR__) || defined(ESP8266)) && defined(USE_SW_SERIAL) #if (defined(__AVR__) || defined(ESP8266)) && defined(USE_SW_SERIAL)
@ -530,121 +454,6 @@ char Adafruit_GPS::read(void) {
return c; return c;
} }
/**************************************************************************/
/*!
@brief Constructor when using SoftwareSerial
@param ser Pointer to SoftwareSerial device
*/
/**************************************************************************/
#if (defined(__AVR__) || defined(ESP8266)) && defined(USE_SW_SERIAL)
Adafruit_GPS::Adafruit_GPS(SoftwareSerial *ser) {
common_init(); // Set everything to common state, then...
gpsSwSerial = ser; // ...override gpsSwSerial with value passed.
}
#endif
/**************************************************************************/
/*!
@brief Constructor when using HardwareSerial
@param ser Pointer to a HardwareSerial object
*/
/**************************************************************************/
Adafruit_GPS::Adafruit_GPS(HardwareSerial *ser) {
common_init(); // Set everything to common state, then...
gpsHwSerial = ser; // ...override gpsHwSerial with value passed.
}
/**************************************************************************/
/*!
@brief Constructor when using I2C
@param theWire Pointer to an I2C TwoWire object
*/
/**************************************************************************/
Adafruit_GPS::Adafruit_GPS(TwoWire *theWire) {
common_init(); // Set everything to common state, then...
gpsI2C = theWire; // ...override gpsI2C
}
/**************************************************************************/
/*!
@brief Constructor when using SPI
@param theSPI Pointer to an SPI device object
@param cspin The pin connected to the GPS CS, can be -1 if unused
*/
/**************************************************************************/
Adafruit_GPS::Adafruit_GPS(SPIClass *theSPI, int8_t cspin) {
common_init(); // Set everything to common state, then...
gpsSPI = theSPI; // ...override gpsSPI
gpsSPI_cs = cspin;
}
/**************************************************************************/
/*!
@brief Initialization code used by all constructor types
*/
/**************************************************************************/
void Adafruit_GPS::common_init(void) {
#if (defined(__AVR__) || defined(ESP8266)) && defined(USE_SW_SERIAL)
gpsSwSerial = NULL; // Set both to NULL, then override correct
#endif
gpsHwSerial = NULL; // port pointer in corresponding constructor
gpsI2C = NULL;
gpsSPI = NULL;
recvdflag = false;
paused = false;
lineidx = 0;
currentline = line1;
lastline = line2;
hour = minute = seconds = year = month = day = fixquality = fixquality_3d =
satellites = 0; // uint8_t
lat = lon = mag = 0; // char
fix = false; // bool
milliseconds = 0; // uint16_t
latitude = longitude = geoidheight = altitude = speed = angle = magvariation =
HDOP = VDOP = PDOP = 0.0; // nmea_float_t
}
/**************************************************************************/
/*!
@brief Start the HW or SW serial port
@param baud_or_i2caddr Baud rate if using serial, I2C address if using I2C
@returns True on successful hardware init, False on failure
*/
/**************************************************************************/
bool Adafruit_GPS::begin(uint32_t baud_or_i2caddr) {
#if (defined(__AVR__) || defined(ESP8266)) && defined(USE_SW_SERIAL)
if (gpsSwSerial) {
gpsSwSerial->begin(baud_or_i2caddr);
}
#endif
if (gpsHwSerial) {
gpsHwSerial->begin(baud_or_i2caddr);
}
if (gpsI2C) {
gpsI2C->begin();
if (baud_or_i2caddr > 0x7F) {
_i2caddr = GPS_DEFAULT_I2C_ADDR;
} else {
_i2caddr = baud_or_i2caddr;
}
// A basic scanner, see if it ACK's
gpsI2C->beginTransmission(_i2caddr);
return (gpsI2C->endTransmission() == 0);
}
if (gpsSPI) {
gpsSPI->begin();
gpsSPI_settings = SPISettings(baud_or_i2caddr, MSBFIRST, SPI_MODE0);
if (gpsSPI_cs >= 0) {
pinMode(gpsSPI_cs, OUTPUT);
digitalWrite(gpsSPI_cs, HIGH);
}
}
delay(10);
return true;
}
/**************************************************************************/ /**************************************************************************/
/*! /*!
@brief Send a command to the GPS device @brief Send a command to the GPS device
@ -680,28 +489,6 @@ char *Adafruit_GPS::lastNMEA(void) {
return (char *)lastline; 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 @brief Wait for a specified sentence from the device
@ -842,6 +629,47 @@ bool Adafruit_GPS::wakeup(void) {
} }
} }
/**************************************************************************/
/*!
@brief Time in seconds since the last position fix was obtained. Will
fail by rolling over to zero after one millis() cycle, about 6-1/2 weeks.
@return nmea_float_t value in seconds since last fix.
*/
/**************************************************************************/
nmea_float_t Adafruit_GPS::secondsSinceFix() {
return (millis() - lastFix) / 1000.;
}
/**************************************************************************/
/*!
@brief Time in seconds since the last GPS time was obtained. Will fail
by rolling over to zero after one millis() cycle, about 6-1/2 weeks.
@return nmea_float_t value in seconds since last GPS time.
*/
/**************************************************************************/
nmea_float_t Adafruit_GPS::secondsSinceTime() {
return (millis() - lastTime) / 1000.;
}
/**************************************************************************/
/*!
@brief Time in seconds since the last GPS date was obtained. Will fail
by rolling over to zero after one millis() cycle, about 6-1/2 weeks.
@return nmea_float_t value in seconds since last GPS date.
*/
/**************************************************************************/
nmea_float_t Adafruit_GPS::secondsSinceDate() {
return (millis() - lastDate) / 1000.;
}
/**************************************************************************/
/*!
@brief Fakes time of receipt of a sentence. Use between build() and parse()
to make the timing look like the sentence arrived from the GPS.
*/
/**************************************************************************/
void Adafruit_GPS::resetSentTime() { sentTime = millis(); }
/**************************************************************************/ /**************************************************************************/
/*! /*!
@brief Checks whether a string starts with a specified prefix @brief Checks whether a string starts with a specified prefix

View File

@ -21,8 +21,6 @@
*/ */
/**************************************************************************/ /**************************************************************************/
// Fllybob added lines 34,35 and 40,41 to add 100mHz logging capability
#ifndef _ADAFRUIT_GPS_H #ifndef _ADAFRUIT_GPS_H
#define _ADAFRUIT_GPS_H #define _ADAFRUIT_GPS_H
@ -31,9 +29,15 @@
Comment out the definition of NMEA_EXTENSIONS to make the library use as Comment out the definition of NMEA_EXTENSIONS to make the library use as
little memory as possible for GPS functionality only. The ARDUINO_ARCH_AVR little memory as possible for GPS functionality only. The ARDUINO_ARCH_AVR
test should leave it out of any compilations for the UNO and similar. */ test should leave it out of any compilations for the UNO and similar. */
#ifndef NMEA_EXTRAS // inject on the compile command line to force extensions
#ifndef ARDUINO_ARCH_AVR #ifndef ARDUINO_ARCH_AVR
#define NMEA_EXTENSIONS ///< if defined will include more NMEA sentences #define NMEA_EXTENSIONS ///< if defined will include more NMEA sentences
#endif #endif
#else
#if (NMEA_EXTRAS > 0)
#define NMEA_EXTENSIONS ///< if defined will include more NMEA sentences
#endif
#endif
#define USE_SW_SERIAL ///< comment this out if you don't want to include #define USE_SW_SERIAL ///< comment this out if you don't want to include
///< software serial in the library ///< software serial in the library
@ -60,8 +64,9 @@
/// type for resulting code from running check() /// type for resulting code from running check()
typedef enum { typedef enum {
NMEA_BAD = 0, ///< passed none of the checks NMEA_BAD = 0, ///< passed none of the checks
NMEA_HAS_DOLLAR = 1, ///< has a dollar sign in the first position NMEA_HAS_DOLLAR =
1, ///< has a dollar sign or exclamation mark in the first position
NMEA_HAS_CHECKSUM = 2, ///< has a valid checksum at the end NMEA_HAS_CHECKSUM = 2, ///< has a valid checksum at the end
NMEA_HAS_NAME = 4, ///< there is a token after the $ followed by a comma NMEA_HAS_NAME = 4, ///< there is a token after the $ followed by a comma
NMEA_HAS_SOURCE = 10, ///< has a recognized source ID NMEA_HAS_SOURCE = 10, ///< has a recognized source ID
@ -75,6 +80,7 @@ typedef enum {
*/ */
class Adafruit_GPS : public Print { class Adafruit_GPS : public Print {
public: public:
// Adafruit_GPS.cpp
bool begin(uint32_t baud_or_i2caddr); bool begin(uint32_t baud_or_i2caddr);
#if (defined(__AVR__) || defined(ESP8266)) && defined(USE_SW_SERIAL) #if (defined(__AVR__) || defined(ESP8266)) && defined(USE_SW_SERIAL)
@ -83,31 +89,60 @@ public:
Adafruit_GPS(HardwareSerial *ser); // Constructor when using HardwareSerial Adafruit_GPS(HardwareSerial *ser); // Constructor when using HardwareSerial
Adafruit_GPS(TwoWire *theWire); // Constructor when using I2C Adafruit_GPS(TwoWire *theWire); // Constructor when using I2C
Adafruit_GPS(SPIClass *theSPI, int8_t cspin); // Constructor when using SPI Adafruit_GPS(SPIClass *theSPI, int8_t cspin); // Constructor when using SPI
Adafruit_GPS(); // Constructor for no communications, just data storage
char *lastNMEA(void);
bool newNMEAreceived();
void common_init(void); void common_init(void);
virtual ~Adafruit_GPS();
void sendCommand(const char *);
void pause(bool b);
uint8_t parseHex(char c);
char read(void);
size_t write(uint8_t);
size_t available(void); size_t available(void);
size_t write(uint8_t);
bool check(char *nmea); char read(void);
bool parse(char *); void sendCommand(const char *);
void addChecksum(char *buff); bool newNMEAreceived();
void pause(bool b);
char *lastNMEA(void);
bool waitForSentence(const char *wait, uint8_t max = MAXWAITSENTENCE,
bool usingInterrupts = false);
bool LOCUS_StartLogger(void);
bool LOCUS_StopLogger(void);
bool LOCUS_ReadStatus(void);
bool standby(void);
bool wakeup(void);
nmea_float_t secondsSinceFix(); nmea_float_t secondsSinceFix();
nmea_float_t secondsSinceTime(); nmea_float_t secondsSinceTime();
nmea_float_t secondsSinceDate(); nmea_float_t secondsSinceDate();
void resetSentTime(); void resetSentTime();
bool wakeup(void); // NMEA_parse.cpp
bool standby(void); 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,
char ref = 'R');
#endif
void addChecksum(char *buff);
// NMEA_data.cpp
void newDataValue(nmea_index_t tag, nmea_float_t v);
#ifdef NMEA_EXTENSIONS
nmea_float_t get(nmea_index_t idx);
nmea_float_t getSmoothed(nmea_index_t idx);
void initDataValue(nmea_index_t idx, char *label = NULL, char *fmt = NULL,
char *unit = NULL, unsigned long response = 0,
nmea_value_type_t type = NMEA_SIMPLE_FLOAT);
nmea_history_t *initHistory(nmea_index_t idx, nmea_float_t scale = 10.0,
nmea_float_t offset = 0.0,
unsigned historyInterval = 20,
unsigned historyN = 192);
void removeHistory(nmea_index_t idx);
void showDataValue(nmea_index_t idx, int n = 7);
bool isCompoundAngle(nmea_index_t idx);
#endif
nmea_float_t boatAngle(nmea_float_t s, nmea_float_t c);
nmea_float_t compassAngle(nmea_float_t s, nmea_float_t c);
int thisCheck = 0; ///< the results of the check on the current sentence int thisCheck = 0; ///< the results of the check on the current sentence
char thisSource[NMEA_MAX_SOURCE_ID] = { char thisSource[NMEA_MAX_SOURCE_ID] = {
@ -162,12 +197,6 @@ public:
uint8_t fixquality_3d; ///< 3D fix quality (1, 3, 3 = Nofix, 2D fix, 3D fix) uint8_t fixquality_3d; ///< 3D fix quality (1, 3, 3 = Nofix, 2D fix, 3D fix)
uint8_t satellites; ///< Number of satellites in use uint8_t satellites; ///< Number of satellites in use
bool waitForSentence(const char *wait, uint8_t max = MAXWAITSENTENCE,
bool usingInterrupts = false);
bool LOCUS_StartLogger(void);
bool LOCUS_StopLogger(void);
bool LOCUS_ReadStatus(void);
uint16_t LOCUS_serial; ///< Log serial number uint16_t LOCUS_serial; ///< Log serial number
uint16_t LOCUS_records; ///< Log number of data record uint16_t LOCUS_records; ///< Log number of data record
uint8_t LOCUS_type; ///< Log type, 0: Overlap, 1: FullStop uint8_t LOCUS_type; ///< Log type, 0: Overlap, 1: FullStop
@ -180,11 +209,20 @@ public:
uint8_t LOCUS_percent; ///< Log life used percentage uint8_t LOCUS_percent; ///< Log life used percentage
#ifdef NMEA_EXTENSIONS #ifdef NMEA_EXTENSIONS
// NMEA additional public functions
char *build(char *nmea, const char *thisSource, const char *thisSentence,
char ref = 'R');
// NMEA additional public variables // NMEA additional public variables
nmea_datavalue_t
val[NMEA_MAX_INDEX]; ///< an array of data value structs, val[0] = most
///< recent HDOP so that ockam indexing works
nmea_float_t depthToKeel =
2.4; ///< depth from surface to bottom of keel in metres
nmea_float_t depthToTransducer =
0.0; ///< depth of transducer below the surface in metres
char toID[NMEA_MAX_WP_ID] = {
0}; ///< id of waypoint going to on this segment of the route
char fromID[NMEA_MAX_WP_ID] = {
0}; ///< id of waypoint coming from on this segment of the route
char txtTXT[63] = {0}; ///< text content from most recent TXT sentence char txtTXT[63] = {0}; ///< text content from most recent TXT sentence
int txtTot = 0; ///< total TXT sentences in group int txtTot = 0; ///< total TXT sentences in group
int txtID = 0; ///< id of the text message int txtID = 0; ///< id of the text message
@ -192,22 +230,41 @@ public:
#endif // NMEA_EXTENSIONS #endif // NMEA_EXTENSIONS
private: private:
// 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); 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); char *parseStr(char *buff, char *p, int n);
bool isEmpty(char *pStart); bool parseTime(char *);
void parseTime(char *);
void parseLat(char *);
bool parseLatDir(char *);
void parseLon(char *);
bool parseLonDir(char *);
bool parseFix(char *); bool parseFix(char *);
bool isEmpty(char *pStart);
// used by check() for validity tests, room for future expansion // used by check() for validity tests, room for future expansion
const char *sources[5] = {"II", "WI", "GP", "GN", const char *sources[6] = {"II", "WI", "GP",
"ZZZ"}; ///< valid source ids "GN", "P", "ZZZ"}; ///< valid source ids
#ifdef NMEA_EXTENSIONS
const char
*sentences_parsed[20] =
{
"GGA", "GLL", "GSA", "RMC", "DBT", "HDM", "HDT",
"MDA", "MTW", "MWV", "RMB", "TXT", "VHW", "VLW",
"VPW", "VWR", "WCV", "XTE", "ZZZ"}; ///< parseable sentence ids
const char *sentences_known[15] = {
"APB", "DPT", "GSV", "HDG", "MWD", "ROT",
"RPM", "RSA", "VDR", "VTG", "ZDA", "ZZZ"}; ///< known, but not parseable
#else // make the lists short to save memory
const char *sentences_parsed[5] = {"GGA", "GLL", "GSA", "RMC", const char *sentences_parsed[5] = {"GGA", "GLL", "GSA", "RMC",
"ZZZ"}; ///< parseable sentence ids "ZZZ"}; ///< parseable sentence ids
const char *sentences_known[1] = { const char *sentences_known[4] = {"DBT", "HDM", "HDT",
"ZZZ"}; ///< known, but not parseable sentence ids "ZZZ"}; ///< known, but not parseable
#endif
// Make all of these times far in the past by setting them near the middle of // Make all of these times far in the past by setting them near the middle of
// the millis() range. Timing assumes that sentences are parsed promptly. // the millis() range. Timing assumes that sentences are parsed promptly.
@ -226,6 +283,7 @@ private:
#if (defined(__AVR__) || defined(ESP8266)) && defined(USE_SW_SERIAL) #if (defined(__AVR__) || defined(ESP8266)) && defined(USE_SW_SERIAL)
SoftwareSerial *gpsSwSerial; SoftwareSerial *gpsSwSerial;
#endif #endif
bool noComms = false;
HardwareSerial *gpsHwSerial; HardwareSerial *gpsHwSerial;
TwoWire *gpsI2C; TwoWire *gpsI2C;
SPIClass *gpsSPI; SPIClass *gpsSPI;

View File

@ -45,10 +45,16 @@
build() will not work properly in an environment that does not support build() will not work properly in an environment that does not support
the %f floating point formatter in sprintf(), and will return NULL. the %f floating point formatter in sprintf(), and will return NULL.
Floating point arguments to sprintf() are explicitly cast to double to
avoid warnings in some compilers.
build() adds Carriage Return and Line Feed to sentences to conform to build() adds Carriage Return and Line Feed to sentences to conform to
NMEA-183, so send your output with a print, not a println. NMEA-183, so send your output with a print, not a println.
The resulting sentence may be corrupted if the input data is corrupt.
In particular, the sentence will be truncated if any of the character
data is 0, e.g. if lat is not set to 'N' or 'S'.
Some of the data in these test sentences may be arbitrary, e.g. for the Some of the data in these test sentences may be arbitrary, e.g. for the
TXT sentence which has a more complicated protocol for multiple lines TXT sentence which has a more complicated protocol for multiple lines
sent as a message set. Also, the data in the class variables are presumed sent as a message set. Also, the data in the class variables are presumed
@ -66,7 +72,8 @@
/**************************************************************************/ /**************************************************************************/
char *Adafruit_GPS::build(char *nmea, const char *thisSource, char *Adafruit_GPS::build(char *nmea, const char *thisSource,
const char *thisSentence, char ref) { const char *thisSentence, char ref) {
sprintf(nmea, "%6.2f", 123.45); // fail if sprintf() doesn't handle floats sprintf(nmea, "%6.2f",
(double)123.45); // fail if sprintf() doesn't handle floats
if (strcmp(nmea, "123.45")) if (strcmp(nmea, "123.45"))
return NULL; return NULL;
*nmea = '$'; *nmea = '$';
@ -82,8 +89,7 @@ char *Adafruit_GPS::build(char *nmea, const char *thisSource,
// pruning excess code easier. Otherwise, keep them alphabetical for ease of // pruning excess code easier. Otherwise, keep them alphabetical for ease of
// reading. // reading.
if (!strcmp(thisSentence, if (!strcmp(thisSentence, "GGA")) { //************************************GGA
"GGA")) { //********************************************GGA
// GGA Global Positioning System Fix Data. Time, Position and fix related // GGA Global Positioning System Fix Data. Time, Position and fix related
// data for a GPS receiver // data for a GPS receiver
// 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 // 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
@ -107,12 +113,12 @@ char *Adafruit_GPS::build(char *nmea, const char *thisSource,
// 14) Differential reference station ID, 0000-1023 // 14) Differential reference station ID, 0000-1023
// 15) Checksum // 15) Checksum
sprintf(p, "%09.2f,%09.4f,%c,%010.4f,%c,%d,%02d,%f,%f,M,%f,M,,", sprintf(p, "%09.2f,%09.4f,%c,%010.4f,%c,%d,%02d,%f,%f,M,%f,M,,",
hour * 10000L + minute * 100L + seconds + milliseconds / 1000., (double)hour * 10000L + minute * 100L + seconds +
milliseconds / 1000.,
(double)latitude, lat, (double)longitude, lon, fixquality, (double)latitude, lat, (double)longitude, lon, fixquality,
satellites, (double)HDOP, (double)altitude, (double)geoidheight); satellites, (double)HDOP, (double)altitude, (double)geoidheight);
} else if (!strcmp(thisSentence, } else if (!strcmp(thisSentence, "GLL")) { //*****************************GLL
"GLL")) { //********************************************GLL
// GLL Geographic Position Latitude/Longitude // GLL Geographic Position Latitude/Longitude
// 1 2 3 4 5 6 7 // 1 2 3 4 5 6 7
// | | | | | | | // | | | | | | |
@ -126,10 +132,10 @@ char *Adafruit_GPS::build(char *nmea, const char *thisSource,
// 7) Checksum // 7) Checksum
sprintf(p, "%09.4f,%c,%010.4f,%c,%09.2f,A", (double)latitude, lat, sprintf(p, "%09.4f,%c,%010.4f,%c,%09.2f,A", (double)latitude, lat,
(double)longitude, lon, (double)longitude, lon,
hour * 10000L + minute * 100L + seconds + milliseconds / 1000.); (double)hour * 10000L + minute * 100L + seconds +
milliseconds / 1000.);
} else if (!strcmp(thisSentence, } else if (!strcmp(thisSentence, "GSA")) { //*****************************GSA
"GSA")) { //********************************************
// GSA GPS DOP and active satellites // GSA GPS DOP and active satellites
// 1 2 3 14 15 16 17 18 // 1 2 3 14 15 16 17 18
// | | | | | | | | // | | | | | | | |
@ -146,8 +152,7 @@ char *Adafruit_GPS::build(char *nmea, const char *thisSource,
// 18) Checksum // 18) Checksum
return NULL; return NULL;
} else if (!strcmp(thisSentence, } else if (!strcmp(thisSentence, "RMC")) { //*****************************RMC
"RMC")) { //********************************************RMC
// RMC Recommended Minimum Navigation Information // RMC Recommended Minimum Navigation Information
// 12 // 12
// 1 2 3 4 5 6 7 8 9 10 11 | // 1 2 3 4 5 6 7 8 9 10 11 |
@ -166,13 +171,257 @@ char *Adafruit_GPS::build(char *nmea, const char *thisSource,
// 11) E or W // 11) E or W
// 12) Checksum // 12) Checksum
sprintf(p, "%09.2f,A,%09.4f,%c,%010.4f,%c,%f,%f,%06d,%f,%c", sprintf(p, "%09.2f,A,%09.4f,%c,%010.4f,%c,%f,%f,%06d,%f,%c",
hour * 10000L + minute * 100L + seconds + milliseconds / 1000., (double)hour * 10000L + minute * 100L + seconds +
milliseconds / 1000.,
(double)latitude, lat, (double)longitude, lon, (double)speed, (double)latitude, lat, (double)longitude, lon, (double)speed,
(double)angle, day * 10000 + month * 100 + year, (double)angle, day * 10000 + month * 100 + year,
(double)magvariation, mag); (double)magvariation, mag);
} else if (!strcmp(thisSentence, } else if (!strcmp(thisSentence, "APB")) { //*****************************APB
"TXT")) { //********************************************TXT // APB Autopilot Sentence "B"
// 13 15
// 1 2 3 4 5 6 7 8 9 10 11 12 | 14 |
// | | | | | | | | | | | | | | |
//$--APB,A,A,x.x,a,N,A,A,x.x,a,c--c,x.x,a,x.x,a*hh
// 1) Status
// V = LORAN-C Blink or SNR warning
// A = general warning flag or other navigation systems when a reliable
// fix is not available
// 2) Status
// V = Loran-C Cycle Lock warning flag
// A = OK or not used
// 3) Cross Track Error Magnitude
// 4) Direction to steer, L or R
// 5) Cross Track Units, N = Nautical Miles
// 6) Status
// A = Arrival Circle Entered
// 7) Status
// A = Perpendicular passed at waypoint
// 8) Bearing origin to destination
// 9) M = Magnetic, T = True
// 10) Destination Waypoint ID
// 11) Bearing, present position to Destination
// 12) M = Magnetic, T = True
// 13) Heading to steer to destination waypoint
// 14) M = Magnetic, T = True
// 15) Checksum
return NULL;
} else if (!strcmp(thisSentence, "DBK")) { //*****************************DBT
// DBK Depth Below Keel
// 1 2 3 4 5 6 7
// | | | | | | |
//$--DBK,x.x,f,x.x,M,x.x,F*hh
// 1) Depth, feet
// 2) f = feet
// 3) Depth, meters
// 4) M = meters
// 5) Depth, Fathoms
// 6) F = Fathoms
// 7) Checksum
return NULL;
} else if (!strcmp(thisSentence, "DBS")) { //*****************************DBT
// DBS Depth Below Surface
// 1 2 3 4 5 6 7
// | | | | | | |
//$--DBS,x.x,f,x.x,M,x.x,F*hh
// 1) Depth, feet
// 2) f = feet
// 3) Depth, meters
// 4) M = meters
// 5) Depth, Fathoms
// 6) F = Fathoms
// 7) Checksum
return NULL;
} else if (!strcmp(thisSentence, "DBT")) { //*****************************DBT
// DBT Depth Below Transducer
// 1 2 3 4 5 6 7
// | | | | | | |
//$--DBT,x.x,f,x.x,M,x.x,F*hh
// 1) Depth, feet
// 2) f = feet
// 3) Depth, meters
// 4) M = meters
// 5) Depth, Fathoms
// 6) F = Fathoms
// 7) Checksum
double d = val[NMEA_DEPTH].latest - depthToTransducer;
sprintf(p, "%f,f,%f,M,,,", d / 0.3048, d);
} else if (!strcmp(thisSentence, "DPT")) { //*****************************DPT
// DPT Heading Deviation & Variation
// 1 2 3
// | | |
//$--DPT,x.x,x.x*hh
// 1) Depth, meters
// 2) Offset from transducer;
// positive means distance from transducer to water line,
// negative means distance from transducer to keel
// 3) Checksum
return NULL;
} else if (!strcmp(thisSentence, "GSV")) { //*****************************GSV
// GSV Satellites in view
// 1 2 3 4 5 6 7 n
// | | | | | | | |
//$--GSV,x,x,x,x,x,x,x,...*hh
// 1) total number of messages
// 2) message number
// 3) satellites in view
// 4) satellite number
// 5) elevation in degrees
// 6) azimuth in degrees to true
// 7) SNR in dB
// more satellite infos like 4)-7)
// n) Checksum
return NULL;
} else if (!strcmp(thisSentence, "HDG")) { //*****************************HDG
// HDG Heading Deviation & Variation
// 1 2 3 4 5 6
// | | | | | |
//$--HDG,x.x,x.x,a,x.x,a*hh
// 1) Magnetic Sensor heading in degrees
// 2) Magnetic Deviation, degrees
// 3) Magnetic Deviation direction, E = Easterly, W = Westerly
// 4) Magnetic Variation degrees
// 5) Magnetic Variation direction, E = Easterly, W = Westerly
// 6) Checksum
return NULL;
} else if (!strcmp(thisSentence, "HDM")) { //*****************************HDM
// HDM Heading Magnetic
// 1 2 3
// | | |
//$--HDM,x.x,M*hh
// 1) Heading Degrees, magnetic
// 2) M = magnetic
// 3) Checksum
sprintf(p, "%f,M", (double)val[NMEA_HDG].latest);
} else if (!strcmp(thisSentence, "HDT")) { //*****************************HDT
// HDT Heading True
// 1 2 3
// | | |
//$--HDT,x.x,T*hh
// 1) Heading Degrees, true
// 2) T = True
// 3) Checksum
// starts with $II for integrated instrumentation
sprintf(p, "%f,T", (double)val[NMEA_HDT].latest);
} else if (!strcmp(thisSentence, "MDA")) { //*****************************MDA
// MDA Meteorological Composite
// 1 2 3 4 5 6 7 8 9 10 11 12
// | | | | | | | | | | | |
//$__MDA,x.x,I,x.x,B,x.x,C,x.x,C,x.x, ,x.x,C,,T,,M,,N,,M*hh
//$IIMDA,,I,,B,,C,21.8,C,,,,C,,T,,M,,N,,M*0F // sent by RayMarine i70s
// Speed/Depth/Wind
// 1) Barometric Pressure
// 2) inches of Hg
// 3) Barometric Pressure
// 4) bar
// 5) Atmospheric Temperature
// 6) C or F
// 7) Water Temperature
// 8) C or F
// 9) Relative Humidity
// 10)
// 11) Dew Point
// 12) C or F
return NULL;
} else if (!strcmp(thisSentence, "MTW")) { //*****************************MTW
// MTW Water Temperature
// 1 2 3
// | | |
//$IIMTW,x.x,C*hh
//$IIMTW,21.8,C*18 // sent by RayMarine i70s Speed/Depth/Wind
// 1) Degrees
// 2) Unit of Measurement, Celcius
// 3) Checksum
return NULL;
} else if (!strcmp(thisSentence, "MWD")) { //*****************************MWD
// MWD Wind Direction & Speed
// Format unknown
return NULL;
} else if (!strcmp(thisSentence, "MWV")) { //*****************************MWV
// MWV Wind Speed and Angle assuming values for True
// 1 2 3 4 5 6
// | | | | | |
//$IIMWV,x.x,a,x.x,a,a*hh
//$WIMWV,276.94,R,0,N,A*03 // sent by RayMarine i70s Speed/Depth/Wind
// 1) Wind Angle, 0 to 360 degrees
// 2) Reference, R = Relative, T = True
// 3) Wind Speed
// 4) Wind Speed Units, K/M/N kilometers/miles/knots
// 5) Status, A = Data Valid
// 6) Checksum
if (ref == 'R')
sprintf(p, "%f,%c,%f,N,A", (double)val[NMEA_AWA].latest, ref,
(double)val[NMEA_AWS].latest);
else
sprintf(p, "%f,%c,%f,N,A", (double)val[NMEA_TWA].latest, 'T',
(double)val[NMEA_TWS].latest);
} else if (!strcmp(thisSentence, "RMB")) { //*****************************RMB
// 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
sprintf(p, ",,,,,,,,,,,%f,A", (double)val[NMEA_VMGWP].latest);
} else if (!strcmp(thisSentence, "ROT")) { //*****************************ROT
// ROT Rate Of Turn
// 1 2 3
// | | |
//$--ROT,x.x,A*hh
// 1) Rate Of Turn, degrees per minute, "-" means bow turns to port
// 2) Status, A means data is valid
// 3) Checksum
return NULL;
} else if (!strcmp(thisSentence, "RPM")) { //*****************************RPM
// RPM Revolutions
// 1 2 3 4 5 6
// | | | | | |
//$--RPM,a,x,x.x,x.x,A*hh
// 1) Source; S = Shaft, E = Engine
// 2) Engine or shaft number
// 3) Speed, Revolutions per minute
// 4) Propeller pitch, % of maximum, "-" means astern
// 5) Status, A means data is valid
// 6) Checksum
return NULL;
} else if (!strcmp(thisSentence, "RSA")) { //*****************************RSA
// RSA Rudder Sensor Angle
// 1 2 3 4 5
// | | | | |
//$--RSA,x.x,A,x.x,A*hh
// 1) Starboard (or single) rudder sensor, "-" means Turn To Port
// 2) Status, A means data is valid
// 3) Port rudder sensor
// 4) Status, A means data is valid
// 5) Checksum
return NULL;
} else if (!strcmp(thisSentence, "TXT")) { //*****************************TXT
// as mentioned in https://github.com/adafruit/Adafruit_GPS/issues/95 // as mentioned in https://github.com/adafruit/Adafruit_GPS/issues/95
// TXT Text Transmission // TXT Text Transmission
// 1 2 3 4 5 // 1 2 3 4 5
@ -185,6 +434,136 @@ char *Adafruit_GPS::build(char *nmea, const char *thisSource,
// 5) Checksum // 5) Checksum
sprintf(p, "01,01,23,This is the text of the sample message"); sprintf(p, "01,01,23,This is the text of the sample message");
} else if (!strcmp(thisSentence, "VDR")) { //*****************************VDR
// VDR Set and Drift
// 1 2 3 4 5 6 7
// | | | | | | |
//$--VDR,x.x,T,x.x,M,x.x,N*hh
// 1) Degress True
// 2) T = True
// 3) Degrees Magnetic
// 4) M = Magnetic
// 5) Knots (speed of current)
// 6) N = Knots
// 7) Checksum
return NULL;
} else if (!strcmp(thisSentence, "VHW")) { //*****************************VHW
// VHW Water Speed and Heading
// 1 2 3 4 5 6 7 8 9
// | | | | | | | | |
//$--VHW,x.x,T,x.x,M,x.x,N,x.x,K*hh
//$IIVHW,,T,,M,0,N,0,K*55 // sent by RayMarine i70s Speed/Depth/Wind
// 1) Degrees True
// 2) T = True
// 3) Degrees Magnetic
// 4) M = Magnetic
// 5) Knots (speed of vessel relative to the water) [66]
// 6) N = Knots
// 7) Kilometers (speed of vessel relative to the water)
// 8) K = Kilometres
// 9) Checksum
sprintf(p, "%f,T,%f,M,%f,N,%f,K", (double)val[NMEA_HDT].latest,
(double)val[NMEA_HDG].latest, (double)val[NMEA_VTW].latest,
(double)val[NMEA_VTW].latest * 1.829);
} else if (!strcmp(thisSentence, "VLW")) { //*****************************VLW
// VLW Distance Traveled through Water
// 1 2 3 4 5
// | | | | |
//$--VLW,x.x,N,x.x,N*hh
//$IIVLW,0,N,0,N,,N,,N*4D // sent by RayMarine i70s Speed/Depth/Wind
// not sure what the last two are?
// 1) Total cumulative distance
// 2) N = Nautical Miles
// 3) Distance since Reset
// 4) N = Nautical Miles
// 5) Checksum
return NULL;
} else if (!strcmp(thisSentence, "VPW")) { //*****************************VPW
// not supported by iNavX
// VPW Speed Measured Parallel to Wind
// 1 2 3 4 5
// | | | | |
//$--VPW,x.x,N,x.x,M*hh
// 1) Speed, "-" means downwind
// 2) N = Knots
// 3) Speed, "-" means downwind
// 4) M = Meters per second
// 5) Checksum
sprintf(p, "%f,N,,", (double)val[NMEA_VMG].latest);
} else if (!strcmp(thisSentence, "VTG")) { //*****************************VTG
// VTG Track Made Good and Ground Speed
// 1 2 3 4 5 6 7 8 9
// | | | | | | | | |
//$--VTG,x.x,T,x.x,M,x.x,N,x.x,K*hh
// 1) Track Degrees 2) T = True
// 3) Track Degrees 4) M = Magnetic
// 5) Speed Knots 6) N = Knots
// 7) Speed Kilometers Per Hour 8) K = Kilometres Per Hour
// 9) Checksum
return NULL;
} else if (!strcmp(thisSentence, "VWR")) { //*****************************VWR
// VWR Relative Wind Speed and Angle
// 1 2 3 4 5 6 7 8 9
// | | | | | | | | |
//$--VWR,x.x,a,x.x,N,x.x,M,x.x,K*hh
//$WIVWR,83.1,L,0,N,0,M,0,K*6D // sent by RayMarine i70s
// Speed/Depth/Wind
// 1) Wind direction magnitude in degrees
// 2) Wind direction Left/Right of bow
// 3) Speed
// 4) N = Knots
// 5) Speed
// 6) M = Meters Per Second
// 7) Speed
// 8) K = Kilometers Per Hour
// 9) Checksum
return NULL;
} else if (!strcmp(thisSentence, "WCV")) { //*****************************WCV
// WCV Waypoint Closure Velocity
// 1 2 3 4
// | | | |
//$--WCV,x.x,N,c--c*hh
// 1) Velocity 2) N = knots 3) Waypoint ID 4) Checksum
sprintf(p, "%f,N,home", (double)val[NMEA_VMG].latest);
} else if (!strcmp(thisSentence, "XTE")) { //*****************************XTE
// XTE Cross-Track Error Measured
// 1 2 3 4 5 6
// | | | | | |
//$--XTE,A,A,x.x,a,N,*hh
// 1) Status
// V = LORAN-C blink or SNR warning
// A = general warning flag or other navigation systems when a reliable
// fix is not available
// 2) Status
// V = Loran-C cycle lock warning flag
// A = OK or not used
// 3) Cross track error magnitude
// 4) Direction to steer, L or R
// 5) Cross track units. N = Nautical Miles
// 6) Checksum
return NULL;
} else if (!strcmp(thisSentence, "ZDA")) { //*****************************ZDA
// ZDA Time & Date UTC, Day, Month, Year and Local Time Zone
// 1 2 3 4 5 6 7
// | | | | | | |
//$--ZDA,hhmmss.ss,xx,xx,xxxx,xx,xx*hh
// 1) Local zone minutes description, same sign as local hours
// 2) Local zone description, 00 to +/- 13 hours
// 3) Year
// 4) Month, 01 to 12
// 5) Day, 01 to 31
// 6) Time (UTC)
// 7) Checksum
return NULL;
} else { } else {
return NULL; // didn't find a match for the build request return NULL; // didn't find a match for the build request
} }
@ -196,3 +575,24 @@ char *Adafruit_GPS::build(char *nmea, const char *thisSource,
} }
#endif // NMEA_EXTENSIONS #endif // NMEA_EXTENSIONS
/**************************************************************************/
/*!
@brief Add *CS where CS is the two character hex checksum for all but
the first character in the string. The checksum is the result of an
exclusive or of all the characters in the string. Also useful if you
are creating new PMTK strings for controlling a GPS module and need a
checksum added.
@param buff Pointer to the string, which must be long enough
@return none
*/
/**************************************************************************/
void Adafruit_GPS::addChecksum(char *buff) {
char cs = 0;
int i = 1;
while (buff[i]) {
cs ^= buff[i];
i++;
}
sprintf(buff, "%s*%02X", buff, cs);
}

566
src/NMEA_data.cpp Normal file
View File

@ -0,0 +1,566 @@
/**************************************************************************/
/*!
@file NMEA_data.cpp
@section intro Introduction
Code for tracking values that change with time so that history can be
examined for recent trends in real time. This code will only generate the
stubs for newDataValue() and data_init(), adding essentially nothing to
the memory footprint unless NMEA_EXTENSIONS is defined.
This is code intended to complement the Adafruit GPS library and process
data for many additional NMEA sentences, mostly of interest to sailors.
The parse function can be a direct substitute for the Adafruit_GPS
function of the same name, updating the same variables within an NMEA
object. A simple use case would involve:
Define an Adafruit_GPS object and use it to collect and parse sentences
from a serial port. The GPS object will be updated and can be used exactly
as usual.
Define an NMEA object and use it to parse the same sentences. It will
succeed on more sentences than the GPS object and keep more detailed data
records. It updates all the same variables as the GPS object, so you could
skip the GPS parsing step.
@section author Author
Written by Rick Sellens.
@section license License
CCBY license
*/
/**************************************************************************/
#include "Adafruit_GPS.h"
/**************************************************************************/
/*!
@brief Update the value and history information with a new value. Call
whenever a new data value is received. The function does nothing if the
NMEA extensions are not enabled.
@param idx The data index for which a new value has been received
@param v The new value received
@return none
*/
/**************************************************************************/
void Adafruit_GPS::newDataValue(nmea_index_t idx, nmea_float_t v) {
#ifdef NMEA_EXTENSIONS
// Serial.println();Serial.print(idx);Serial.print(", "); Serial.println(v);
val[idx].latest = v; // update the value
// update the smoothed verion
if (isCompoundAngle(idx)) { // angle with sin/cos component recording
newDataValue((nmea_index_t)(idx + 1), sin(v / RAD_TO_DEG));
newDataValue((nmea_index_t)(idx + 2), cos(v / RAD_TO_DEG));
}
// weighting factor for smoothing depends on delta t / tau
nmea_float_t w =
min((nmea_float_t)1.0,
((nmea_float_t)millis() - val[idx].lastUpdate) / val[idx].response);
// default smoothing
val[idx].smoothed = (1.0 - w) * val[idx].smoothed + w * v;
// special smoothing for some angle types
if (val[idx].type == NMEA_COMPASS_ANGLE_SIN)
val[idx].smoothed =
compassAngle(val[idx + 1].smoothed, val[idx + 2].smoothed);
if (val[idx].type == NMEA_BOAT_ANGLE_SIN)
val[idx].smoothed = boatAngle(val[idx + 1].smoothed, val[idx + 2].smoothed);
// some types just don't make sense to smooth -- use latest
if (val[idx].type == NMEA_BOAT_ANGLE)
val[idx].smoothed = val[idx].latest;
if (val[idx].type == NMEA_COMPASS_ANGLE)
val[idx].smoothed = val[idx].latest;
if (val[idx].type == NMEA_DDMM)
val[idx].smoothed = val[idx].latest;
if (val[idx].type == NMEA_HHMMSS)
val[idx].smoothed = val[idx].latest;
val[idx].lastUpdate = millis(); // take a time stamp
if (val[idx].hist) { // there's a history struct for this tag
unsigned long seconds = (millis() - val[idx].hist->lastHistory) / 1000;
// do an update if the time has come, or if this is the first time through
if (seconds >= val[idx].hist->historyInterval ||
val[idx].hist->lastHistory == 0) {
// move the old history back in time by one step
for (unsigned i = 0; i < (val[idx].hist->n - 1); i++)
val[idx].hist->data[i] = val[idx].hist->data[i + 1];
// Create the new entry, scaling and offsetting the value to fit into an
// integer, and based on the smoothed value.
val[idx].hist->data[val[idx].hist->n - 1] =
val[idx].hist->scale * (val[idx].smoothed - val[idx].hist->offset);
val[idx].hist->lastHistory = millis();
}
}
#endif // NMEA_EXTENSIONS
}
/**************************************************************************/
/*!
@brief Initialize the object. Build a val[] matrix of data values for
all of the enumerated values, including the extra values for the compound
angle types. The initializer shold probably leave it up to the user
sketch to decide which data values should carry the extra memory burden
of history.
@return none
*/
/**************************************************************************/
void Adafruit_GPS::data_init() {
#ifdef NMEA_EXTENSIONS
// fill all the data values with nothing
static char c[] = "NUL";
for (int i = 0; i < (int)NMEA_MAX_INDEX; i++) {
initDataValue((nmea_index_t)i, c, NULL, NULL, 0, (nmea_value_type_t)0);
}
// fill selected data values with the relevant information and pointers
static char BoatSpeedfmt[] = "%6.2f";
static char WindSpeedfmt[] = "%6.1f";
static char Speedunit[] = "knots";
static char Anglefmt[] = "%6.0f";
static char BoatAngleunit[] = "Degrees";
static char TrueAngleunit[] = "Deg True";
static char MagAngleunit[] = "Deg Mag";
static char HDOPlabel[] = "HDOP";
initDataValue(NMEA_HDOP, HDOPlabel);
static char LATlabel[] = "Lat";
static char LATfmt[] = "%9.4f";
static char LATunit[] = "DDD.dddd";
initDataValue(
NMEA_LAT, LATlabel, LATfmt, LATunit, 0,
NMEA_BOAT_ANGLE); // angle from -180 to 180, or actually -90 to 90 for lat
static char LONlabel[] = "Lon";
initDataValue(NMEA_LON, LONlabel, LATfmt, LATunit, 0,
NMEA_BOAT_ANGLE); // angle from -180 to 180
static char LATWPlabel[] = "WP Lat";
initDataValue(NMEA_LATWP, LATWPlabel, LATfmt, LATunit, 0, NMEA_BOAT_ANGLE);
static char LONWPlabel[] = "WP Lon";
initDataValue(NMEA_LONWP, LONWPlabel, LATfmt, LATunit, 0, NMEA_BOAT_ANGLE);
static char SOGlabel[] = "SOG";
initDataValue(NMEA_SOG, SOGlabel, BoatSpeedfmt, Speedunit);
static char COGlabel[] = "COG";
// types with sin/cos need two extra spots in the values matrix!
initDataValue(NMEA_COG, COGlabel, Anglefmt, TrueAngleunit, 0,
NMEA_COMPASS_ANGLE_SIN); // type: 0-360 angle with sin/cos 11
static char COGWPlabel[] = "WP COG";
initDataValue(NMEA_COGWP, COGWPlabel, Anglefmt, TrueAngleunit, 0,
NMEA_COMPASS_ANGLE); // type: angle 0-360 1
static char XTElabel[] = "XTE";
static char XTEfmt[] = "%6.2f";
static char XTEunit[] = "NM";
initDataValue(NMEA_XTE, XTElabel, XTEfmt, XTEunit);
static char DISTWPlabel[] = "WP Dist";
initDataValue(NMEA_DISTWP, DISTWPlabel, XTEfmt, XTEunit);
static char AWAlabel[] = "AWA";
initDataValue(NMEA_AWA, AWAlabel, Anglefmt, BoatAngleunit, 0,
NMEA_BOAT_ANGLE_SIN); // type: +-180 angle with sin/cos 12
static char AWSlabel[] = "AWS";
initDataValue(NMEA_AWS, AWSlabel, WindSpeedfmt, Speedunit);
static char TWAlabel[] = "TWA";
initDataValue(NMEA_TWA, TWAlabel, Anglefmt, BoatAngleunit, 0,
NMEA_BOAT_ANGLE_SIN); // type: +-180 angle with sin/cos 12
static char TWDlabel[] = "TWD";
initDataValue(NMEA_TWD, TWDlabel, Anglefmt, TrueAngleunit, 0,
NMEA_COMPASS_ANGLE_SIN); // type: 0-360 angle with sin/cos 11
static char TWSlabel[] = "TWS";
initDataValue(NMEA_TWS, TWSlabel, WindSpeedfmt, Speedunit);
static char VMGlabel[] = "VMG";
initDataValue(NMEA_VMG, VMGlabel, BoatSpeedfmt, Speedunit);
static char VMGWPlabel[] = "WP VMG";
initDataValue(NMEA_VMGWP, VMGWPlabel, BoatSpeedfmt, Speedunit);
static char HEELlabel[] = "Heel";
static char HEELunit[] = "Deg Stbd";
initDataValue(NMEA_HEEL, HEELlabel, Anglefmt, HEELunit, 0,
NMEA_BOAT_ANGLE); // type: angle +/-180 2
static char PITCHlabel[] = "Pitch";
static char PITCHunit[] = "Deg Bow Up";
initDataValue(NMEA_PITCH, PITCHlabel, Anglefmt, PITCHunit, 0,
NMEA_BOAT_ANGLE); // type: angle +/-180 2
static char HDGlabel[] = "HDG";
initDataValue(NMEA_HDG, HDGlabel, Anglefmt, MagAngleunit, 0,
NMEA_COMPASS_ANGLE_SIN); // type: 0-360 angle with sin/cos 11
static char HDTlabel[] = "HDG";
initDataValue(NMEA_HDT, HDTlabel, Anglefmt, TrueAngleunit, 0,
NMEA_COMPASS_ANGLE_SIN); // type: 0-360 angle with sin/cos 11
static char VTWlabel[] = "VTW";
initDataValue(NMEA_VTW, VTWlabel, BoatSpeedfmt, Speedunit);
static char LOGlabel[] = "Log";
static char LOGfmt[] = "%6.0f";
static char LOGunit[] = "NM";
initDataValue(NMEA_LOG, LOGlabel, LOGfmt, LOGunit);
static char LOGRlabel[] = "Trip";
static char LOGRfmt[] = "%6.2f";
initDataValue(NMEA_LOG, LOGRlabel, LOGRfmt, LOGunit);
static char DEPTHlabel[] = "Depth";
static char DEPTHfmt[] = "%6.1f";
static char DEPTHunit[] = "m";
initDataValue(NMEA_DEPTH, DEPTHlabel, DEPTHfmt, DEPTHunit);
static char RPM_M1label[] = "Motor 1";
static char RPM_M1fmt[] = "%6.0f";
static char RPM_M1unit[] = "RPM";
initDataValue(NMEA_RPM_M1, RPM_M1label, RPM_M1fmt, RPM_M1unit);
static char TEMPERATURE_M1label[] = "Temp 1";
static char TEMPERATURE_M1fmt[] = "%6.0f";
static char TEMPERATURE_M1unit[] = "Deg C";
initDataValue(NMEA_TEMPERATURE_M1, TEMPERATURE_M1label, TEMPERATURE_M1fmt,
TEMPERATURE_M1unit);
static char PRESSURE_M1label[] = "Oil 1";
static char PRESSURE_M1fmt[] = "%6.0f";
static char PRESSURE_M1unit[] = "kPa";
initDataValue(NMEA_PRESSURE_M1, PRESSURE_M1label, PRESSURE_M1fmt,
PRESSURE_M1unit);
static char VOLTAGE_M1label[] = "Motor 1";
static char VOLTAGE_M1fmt[] = "%6.2f";
static char VOLTAGE_M1unit[] = "Volts";
initDataValue(NMEA_VOLTAGE_M1, VOLTAGE_M1label, VOLTAGE_M1fmt,
VOLTAGE_M1unit);
static char CURRENT_M1label[] = "Motor 1";
static char CURRENT_M1fmt[] = "%6.1f";
static char CURRENT_M1unit[] = "Amps";
initDataValue(NMEA_CURRENT_M1, CURRENT_M1label, CURRENT_M1fmt,
CURRENT_M1unit);
static char RPM_M2label[] = "Motor 2";
initDataValue(NMEA_RPM_M2, RPM_M2label, RPM_M1fmt, RPM_M1unit);
static char TEMPERATURE_M2label[] = "Temp 2";
initDataValue(NMEA_TEMPERATURE_M2, TEMPERATURE_M2label, TEMPERATURE_M1fmt,
TEMPERATURE_M1unit);
static char PRESSURE_M2label[] = "Oil 2";
initDataValue(NMEA_PRESSURE_M2, PRESSURE_M2label, PRESSURE_M1fmt,
PRESSURE_M1unit);
static char VOLTAGE_M2label[] = "Motor 2";
initDataValue(NMEA_VOLTAGE_M2, VOLTAGE_M2label, VOLTAGE_M1fmt,
VOLTAGE_M1unit);
static char CURRENT_M2label[] = "Motor 2";
initDataValue(NMEA_CURRENT_M2, CURRENT_M2label, CURRENT_M1fmt,
CURRENT_M1unit);
static char TEMPERATURE_AIRlabel[] = "Air";
static char TEMPERATURE_AIRfmt[] = "%6.1f";
static char TEMPERATURE_AIRunit[] = "Deg C";
initDataValue(NMEA_TEMPERATURE_AIR, TEMPERATURE_AIRlabel, TEMPERATURE_AIRfmt,
TEMPERATURE_AIRunit);
static char TEMPERATURE_WATERlabel[] = "Water";
static char TEMPERATURE_WATERfmt[] = "%6.1f";
static char TEMPERATURE_WATERunit[] = "Deg C";
initDataValue(NMEA_TEMPERATURE_WATER, TEMPERATURE_WATERlabel,
TEMPERATURE_WATERfmt, TEMPERATURE_WATERunit);
static char HUMIDITYlabel[] = "Humidity";
static char HUMIDITYfmt[] = "%6.0f";
static char HUMIDITYunit[] = "% RH";
initDataValue(NMEA_HUMIDITY, HUMIDITYlabel, HUMIDITYfmt, HUMIDITYunit);
static char BAROMETERlabel[] = "Barometer";
static char BAROMETERfmt[] = "%6.0f";
static char BAROMETERunit[] = "Pa";
initDataValue(NMEA_BAROMETER, BAROMETERlabel, BAROMETERfmt, BAROMETERunit);
#endif // NMEA_EXTENSIONS
}
#ifdef NMEA_EXTENSIONS
/**************************************************************************/
/*!
@brief Clearer approach to retrieving NMEA values by allowing calls that
look like nmea.get(NMEA_TWA) instead of val[NMEA_TWA].latest.
Use newDataValue() to set the values.
@param idx the NMEA value's index
@return the latest NMEA value
*/
/**************************************************************************/
nmea_float_t Adafruit_GPS::get(nmea_index_t idx) {
if (idx >= NMEA_MAX_INDEX || idx < NMEA_HDOP)
return 0.0;
return val[idx].latest;
}
/**************************************************************************/
/*!
@brief Clearer approach to retrieving NMEA values
@param idx the NMEA value's index
@return the latest NMEA value, smoothed
*/
/**************************************************************************/
nmea_float_t Adafruit_GPS::getSmoothed(nmea_index_t idx) {
if (idx >= NMEA_MAX_INDEX || idx < NMEA_HDOP)
return 0.0;
return val[idx].smoothed;
}
/**************************************************************************/
/*!
@brief Initialize the contents of a data value table entry
@param idx The data index for the value to be initialized
@param label Pointer to a label string that describes the value
@param fmt Pointer to a sprintf format to use for the value, e.g. "%6.2f"
@param unit Pointer to a string for the units, e.g. "Deg Mag"
@param response Time constant for smoothing in ms. The longer the time
constant, the more slowly the smoothed value will move towards a new value.
@param type The type of data contained in the value. simple float 0,
angle 0-360 1, angle +/-180 2, angle with history centered +/- around
the latest angle 3, lat/lon DDMM.mm 10, time HHMMSS 20.
@return none
*/
/**************************************************************************/
void Adafruit_GPS::initDataValue(nmea_index_t idx, char *label, char *fmt,
char *unit, unsigned long response,
nmea_value_type_t type) {
if (idx < NMEA_MAX_INDEX) {
if (label)
val[idx].label = label;
if (fmt)
val[idx].fmt = fmt;
if (unit)
val[idx].unit = unit;
if (response)
val[idx].response = response;
val[idx].type = type;
if ((int)(val[idx].type / 10) ==
1) { // angle with sin/cos component recording
initDataValue((nmea_index_t)(
idx + 1)); // initialize the next two data values as well
initDataValue((nmea_index_t)(idx + 2));
}
}
}
/**************************************************************************/
/*!
@brief Attempt to add history to a data value table entry. If it fails
to malloc the space, history will not be added. Test the pointer for a
check if needed.
@param idx The data index for the value to have history recorded
@param scale Value for scaling the integer history list
@param offset Value for scaling the integer history list
@param historyInterval Approximate Time in seconds between historical
values.
@return pointer to the history
*/
/**************************************************************************/
nmea_history_t *Adafruit_GPS::initHistory(nmea_index_t idx, nmea_float_t scale,
nmea_float_t offset,
unsigned historyInterval,
unsigned historyN) {
historyN = max((unsigned)10, historyN);
if (idx < NMEA_MAX_INDEX) {
// remove any existing history
if (val[idx].hist != NULL)
removeHistory(idx);
// space for the struct
val[idx].hist = (nmea_history_t *)malloc(sizeof(nmea_history_t));
if (val[idx].hist != NULL) {
// space for the data array of the appropriate size
val[idx].hist->data = (int16_t *)malloc(sizeof(int16_t) * historyN);
if (val[idx].hist->data != NULL) {
// initialize the data array
for (unsigned i = 0; i < historyN; i++)
val[idx].hist->data[i] = 0;
} else
free(val[idx].hist);
}
if (val[idx].hist != NULL) {
val[idx].hist->n = historyN;
if (scale > 0.0)
val[idx].hist->scale = scale;
val[idx].hist->offset = offset;
if (historyInterval > 0)
val[idx].hist->historyInterval = historyInterval;
}
return val[idx].hist;
}
return NULL;
}
/**************************************************************************/
/*!
@brief Remove history from a data value table entry, if it has been added.
@param idx The data index for the value to have history removed
@return none
*/
/**************************************************************************/
void Adafruit_GPS::removeHistory(nmea_index_t idx) {
if (idx < NMEA_MAX_INDEX) {
if (val[idx].hist == NULL)
return;
free(val[idx].hist->data);
free(val[idx].hist);
val[idx].hist = NULL;
}
}
/**************************************************************************/
/*!
@brief Print out the current state of a data value. Primarily useful as
a debugging aid.
@param idx The index for the data value
@param n The number of history values to include
@return none
*/
/**************************************************************************/
void Adafruit_GPS::showDataValue(nmea_index_t idx, int n) {
Serial.print("idx: ");
if (idx < 10)
Serial.print(" ");
Serial.print(idx);
Serial.print(", ");
Serial.print(val[idx].label);
Serial.print(", ");
Serial.print(val[idx].latest, 4);
Serial.print(", ");
Serial.print(val[idx].smoothed, 4);
Serial.print(", at ");
Serial.print(val[idx].lastUpdate);
Serial.print(" ms, tau = ");
Serial.print(val[idx].response);
Serial.print(" ms, type:");
Serial.print(val[idx].type);
Serial.print(", ockam:");
Serial.print(val[idx].ockam);
if (val[idx].hist) {
Serial.print("\n History at ");
Serial.print(val[idx].hist->historyInterval);
Serial.print(" second intervals: ");
Serial.print(val[idx].hist->data[val[idx].hist->n - 1]);
for (unsigned i = val[idx].hist->n - 2;
i >= max(val[idx].hist->n - n, (unsigned)0);
i--) { // most recent first
Serial.print(", ");
Serial.print(val[idx].hist->data[i]);
}
}
Serial.print("\n");
if (idx == NMEA_LAT) {
Serial.print(" latitude (DDMM.mmmm): ");
Serial.print(latitude, 4);
Serial.print(", lat: ");
Serial.print(lat);
Serial.print(", latitudeDegrees: ");
Serial.print(latitudeDegrees, 8);
Serial.print(", latitude_fixed: ");
Serial.println(latitude_fixed);
}
if (idx == NMEA_LON) {
Serial.print(" longitude (DDMM.mmmm): ");
Serial.print(longitude, 4);
Serial.print(", lon: ");
Serial.print(lon);
Serial.print(", longitudeDegrees: ");
Serial.print(longitudeDegrees, 8);
Serial.print(", longitude_fixed: ");
Serial.println(longitude_fixed);
}
}
/**************************************************************************/
/*!
@brief Check if it is a compound angle
@param idx The index for the data value
@return true if a compound angle requiring 3 contiguos data values.
*/
/**************************************************************************/
bool Adafruit_GPS::isCompoundAngle(nmea_index_t idx) {
if ((int)(val[idx].type / 10) == 1) // angle with sin/cos component recording
return true;
return false;
}
/**************************************************************************/
/*!
@brief Estimate a direction in -180 to 180 degree range from the values
of the sine and cosine of the compound angle, which could be noisy.
@param s The sin of the angle
@param c The cosine of the angle
@return The angle in -180 to 180 degree range.
*/
/**************************************************************************/
nmea_float_t Adafruit_GPS::boatAngle(nmea_float_t s, nmea_float_t c) {
nmea_float_t sAng =
asin(s) * RAD_TO_DEG; // put the sin angle in -90 to 90 range
while (sAng < -90)
sAng += 180.;
while (sAng > 90)
sAng -= 180.;
nmea_float_t cAng =
acos(c) * RAD_TO_DEG; // put the cos angle in 0 to 180 range
while (cAng < 0)
cAng += 180.;
while (cAng > 180)
cAng -= 180.;
// Pick the most accurate representation and translate
if (cAng < 45)
return sAng; // Close hauled
else {
if (cAng > 135) { // Running
if (sAng > 0)
return 180 - sAng; // on starboard tack
else
return -180 - sAng; // on port tack
} else { // Reaching
if (sAng < 0)
return -cAng; // on port tack
else
return cAng; // on starboard tack
}
}
return 9999; // you can't get here, but there must be an explicit return
}
/**************************************************************************/
/*!
@brief Estimate a direction in 0 to 360 degree range from the values
of the sine and cosine of the compound angle, which could be noisy.
@param s The sin of the angle
@param c The cosine of the angle
@return The angle in 0 to 360 degree range.
*/
/**************************************************************************/
nmea_float_t Adafruit_GPS::compassAngle(nmea_float_t s, nmea_float_t c) {
nmea_float_t ang = boatAngle(s, c);
if (ang < 5000) { // if reasonable range
while (ang < 0)
ang += 360.; // round up
while (ang > 360)
ang -= 360.; // round down
}
return ang;
}
#endif // NMEA_EXTENSIONS

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 @param nmea Pointer to the NMEA string
@return True if we parsed it, false if it has an invalid checksum or invalid @return True if successfully parsed, false if fails check or parsing
data
*/ */
/**************************************************************************/ /**************************************************************************/
bool Adafruit_GPS::parse(char *nmea) { bool Adafruit_GPS::parse(char *nmea) {
// do checksum check
if (!check(nmea)) if (!check(nmea))
return false; return false;
// passed the check, so there's a valid source in thisSource and a valid // passed the check, so there's a valid source in thisSource and a valid
// sentence in thisSentence // sentence in thisSentence
// look for a few common sentences
char *p = nmea; // Pointer to move through the sentence -- good parsers are char *p = nmea; // Pointer to move through the sentence -- good parsers are
// non-destructive // non-destructive
p = strchr(p, ',') + p = strchr(p, ',') + 1; // Skip to char after the next comma, then check.
1; // Skip to the character after the next comma, then check sentence.
if (!strcmp(thisSentence, "GGA")) { // This may look inefficient, but an M0 will get down the list in about 1 us /
// found GGA // strcmp()! Put the GPS sentences from Adafruit_GPS at the top to make
// get time // 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); parseTime(p);
p = strchr(p, ',') + 1; // parse time with specialized function
// parse out latitude // 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;
parseLat(p);
p = strchr(p, ',') + 1; p = strchr(p, ',') + 1;
if (!parseLatDir(p)) // parse out both longitude and direction, then go to next field, or fail
return false; if (parseCoord(p, &longitudeDegrees, &longitude, &longitude_fixed, &lon))
newDataValue(NMEA_LON, longitudeDegrees);
// parse out longitude
p = strchr(p, ',') + 1; p = strchr(p, ',') + 1;
parseLon(p);
p = strchr(p, ',') + 1; p = strchr(p, ',') + 1;
if (!parseLonDir(p)) if (!isEmpty(p)) { // if it's a , (or a * at end of sentence) the value is
return false; // not included
fixquality = atoi(p); // needs additional processing
p = strchr(p, ',') + 1;
if (!isEmpty(p)) {
fixquality = atoi(p);
if (fixquality > 0) { if (fixquality > 0) {
fix = true; fix = true;
lastFix = sentTime; lastFix = sentTime;
} else } else
fix = false; fix = false;
} }
p = strchr(p, ',') + 1; // then move on to the next
p = strchr(p, ',') + 1; // Most can just be parsed with atoi() or atof(), then move on to the next.
if (!isEmpty(p)) { if (!isEmpty(p))
satellites = atoi(p); satellites = atoi(p);
}
p = strchr(p, ',') + 1; p = strchr(p, ',') + 1;
if (!isEmpty(p)) { if (!isEmpty(p))
HDOP = atof(p); newDataValue(NMEA_HDOP, HDOP = atof(p));
}
p = strchr(p, ',') + 1; p = strchr(p, ',') + 1;
if (!isEmpty(p)) { if (!isEmpty(p))
altitude = atof(p); altitude = atof(p);
}
p = strchr(p, ',') + 1; p = strchr(p, ',') + 1;
p = strchr(p, ',') + 1; p = strchr(p, ',') + 1; // skip the units
if (!isEmpty(p)) { if (!isEmpty(p))
geoidheight = atof(p); geoidheight = atof(p); // skip the rest
}
}
else if (!strcmp(thisSentence, "RMC")) { } else if (!strcmp(thisSentence, "RMC")) { //*****************************RMC
// found RMC // in Adafruit from Actisense NGW-1 from SH CP150C
// get time
parseTime(p); parseTime(p);
// fix or no fix
p = strchr(p, ',') + 1; p = strchr(p, ',') + 1;
if (!parseFix(p)) parseFix(p);
return false;
// parse out latitude
p = strchr(p, ',') + 1; 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; p = strchr(p, ',') + 1;
if (!parseLatDir(p))
return false;
// parse out longitude
p = strchr(p, ',') + 1; 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; p = strchr(p, ',') + 1;
if (!parseLonDir(p))
return false;
// speed
p = strchr(p, ',') + 1; p = strchr(p, ',') + 1;
if (!isEmpty(p)) { if (!isEmpty(p))
speed = atof(p); newDataValue(NMEA_SOG, speed = atof(p));
}
// angle
p = strchr(p, ',') + 1; p = strchr(p, ',') + 1;
if (!isEmpty(p)) { if (!isEmpty(p))
angle = atof(p); newDataValue(NMEA_COG, angle = atof(p));
}
p = strchr(p, ',') + 1; p = strchr(p, ',') + 1;
if (!isEmpty(p)) { if (!isEmpty(p)) {
uint32_t fulldate = atof(p); uint32_t fulldate = atof(p);
@ -145,66 +135,264 @@ bool Adafruit_GPS::parse(char *nmea) {
month = (fulldate % 10000) / 100; month = (fulldate % 10000) / 100;
year = (fulldate % 100); year = (fulldate % 100);
lastDate = sentTime; lastDate = sentTime;
} } // skip the rest
}
else if (!strcmp(thisSentence, "GLL")) { } else if (!strcmp(thisSentence, "GLL")) { //*****************************GLL
// found GLL // in Adafruit from Actisense NGW-1 from SH CP150C
// parse out latitude // parse out both latitude and direction, then go to next field, or fail
parseLat(p); if (parseCoord(p, &latitudeDegrees, &latitude, &latitude_fixed, &lat))
newDataValue(NMEA_LAT, latitudeDegrees);
p = strchr(p, ',') + 1; p = strchr(p, ',') + 1;
if (!parseLatDir(p))
return false;
// parse out longitude
p = strchr(p, ',') + 1; 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; p = strchr(p, ',') + 1;
if (!parseLonDir(p))
return false;
// get time
p = strchr(p, ',') + 1; p = strchr(p, ',') + 1;
parseTime(p); parseTime(p);
// fix or no fix
p = strchr(p, ',') + 1; p = strchr(p, ',') + 1;
if (!parseFix(p)) parseFix(p); // skip the rest
return false;
}
else if (!strcmp(thisSentence, "GSA")) { } else if (!strcmp(thisSentence, "GSA")) { //*****************************GSA
// found GSA // in Adafruit from Actisense NGW-1
// parse out Auto selection, but ignore them p = strchr(p, ',') + 1; // skip selection mode
// parse out 3d fixquality if (!isEmpty(p))
p = strchr(p, ',') + 1;
if (!isEmpty(p)) {
fixquality_3d = atoi(p); fixquality_3d = atoi(p);
} p = strchr(p, ',') + 1;
// skip 12 Satellite PDNs without interpreting them // skip 12 Satellite PDNs without interpreting them
for (int i = 0; i < 12; i++) for (int i = 0; i < 12; i++)
p = strchr(p, ',') + 1; p = strchr(p, ',') + 1;
if (!isEmpty(p))
// parse out PDOP
p = strchr(p, ',') + 1;
if (!isEmpty(p)) {
PDOP = atof(p); PDOP = atof(p);
} p = strchr(p, ',') + 1;
// parse out HDOP, we also parse this from the GGA sentence. Chipset should // parse out HDOP, we also parse this from the GGA sentence. Chipset should
// report the same for both // report the same for both
if (!isEmpty(p))
newDataValue(NMEA_HDOP, HDOP = atof(p));
p = strchr(p, ',') + 1; p = strchr(p, ',') + 1;
if (!isEmpty(p)) { if (!isEmpty(p))
HDOP = atof(p); VDOP = atof(p); // last before checksum
}
// parse out VDOP
p = strchr(p, ',') + 1;
if (!isEmpty(p)) {
VDOP = atof(p);
}
}
}
#ifdef NMEA_EXTENSIONS // Sentences not required for basic GPS functionality #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)) if (!isEmpty(p))
txtTot = atoi(p); txtTot = atoi(p);
p = strchr(p, ',') + 1; p = strchr(p, ',') + 1;
@ -216,12 +404,128 @@ bool Adafruit_GPS::parse(char *nmea) {
p = strchr(p, ',') + 1; p = strchr(p, ',') + 1;
if (!isEmpty(p)) if (!isEmpty(p))
parseStr(txtTXT, p, 61); // copy the text to NMEA TXT max of 61 characters 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 #endif // NMEA_EXTENSIONS
// we dont parse the remaining, yet! else {
else return false; // didn't find the required sentence definition
return false; }
// Record the successful parsing of where the last data came from and when // Record the successful parsing of where the last data came from and when
strcpy(lastSource, thisSource); strcpy(lastSource, thisSource);
@ -229,3 +533,300 @@ bool Adafruit_GPS::parse(char *nmea) {
lastUpdate = millis(); lastUpdate = millis();
return true; 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;
}