Merge pull request #113 from sellensr/master
Add data values and history to NMEA_EXTENSIONS, plus more sentences
This commit is contained in:
commit
a57c52f242
|
|
@ -34,9 +34,10 @@ Adafruit_GPS GPS(&GPSSerial);
|
|||
|
||||
#ifdef NMEA_EXTENSIONS
|
||||
// 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
|
||||
// sentences from the Boat data to feed to GPS for testing.
|
||||
Adafruit_GPS Boat(&GPSSerial);
|
||||
// communications, so you don't need to call Boat.begin() in setup.
|
||||
// We will build some fake sentences from the Boat data to feed to
|
||||
// GPS for testing.
|
||||
Adafruit_GPS Boat;
|
||||
#endif
|
||||
|
||||
// Set GPSECHO to 'false' to turn off echoing the GPS data to the Serial console
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -32,117 +32,6 @@
|
|||
|
||||
static bool strStartsWith(const char *str, const char *prefix);
|
||||
|
||||
/**************************************************************************/
|
||||
/*!
|
||||
@brief Check an NMEA string for basic format, valid source ID and valid
|
||||
and valid sentence ID. Update the values of thisCheck, thisSource and
|
||||
thisSentence.
|
||||
@param nmea Pointer to the NMEA string
|
||||
@return True if well formed, false if it has problems
|
||||
*/
|
||||
/**************************************************************************/
|
||||
bool Adafruit_GPS::check(char *nmea) {
|
||||
thisCheck = 0; // new check
|
||||
if (*nmea != '$')
|
||||
return false; // doesn't start with $
|
||||
else
|
||||
thisCheck += NMEA_HAS_DOLLAR;
|
||||
// do checksum check -- first look if we even have one -- ignore all but last
|
||||
// *
|
||||
char *ast = nmea; // not strchr(nmea,'*'); for first *
|
||||
while (*ast)
|
||||
ast++; // go to the end
|
||||
while (*ast != '*' && ast > nmea)
|
||||
ast--; // then back to * if it's there
|
||||
if (*ast != '*')
|
||||
return false; // there is no asterisk
|
||||
else {
|
||||
uint16_t sum = parseHex(*(ast + 1)) * 16; // extract checksum
|
||||
sum += parseHex(*(ast + 2));
|
||||
char *p = nmea; // check checksum
|
||||
for (char *p1 = p + 1; p1 < ast; p1++)
|
||||
sum ^= *p1;
|
||||
if (sum != 0)
|
||||
return false; // bad checksum :(
|
||||
else
|
||||
thisCheck += NMEA_HAS_CHECKSUM;
|
||||
}
|
||||
// extract source of variable length
|
||||
char *p = nmea + 1;
|
||||
const char *src = tokenOnList(p, sources);
|
||||
if (src) {
|
||||
strcpy(thisSource, src);
|
||||
thisCheck += NMEA_HAS_SOURCE;
|
||||
} else
|
||||
return false;
|
||||
p += strlen(src);
|
||||
// extract sentence id and check if parsed
|
||||
const char *snc = tokenOnList(p, sentences_parsed);
|
||||
if (snc) {
|
||||
strcpy(thisSentence, snc);
|
||||
thisCheck += NMEA_HAS_SENTENCE_P + NMEA_HAS_SENTENCE;
|
||||
} else { // check if known
|
||||
snc = tokenOnList(p, sentences_known);
|
||||
if (snc) {
|
||||
strcpy(thisSentence, snc);
|
||||
thisCheck += NMEA_HAS_SENTENCE;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true; // passed all the tests
|
||||
}
|
||||
|
||||
/**************************************************************************/
|
||||
/*!
|
||||
@brief Check if a token at the start of a string is on a list.
|
||||
@param token Pointer to the string
|
||||
@param list A list of strings, with the final entry starting "ZZ"
|
||||
@return Pointer to the found token, or NULL if it fails
|
||||
*/
|
||||
/**************************************************************************/
|
||||
const char *Adafruit_GPS::tokenOnList(char *token, const char **list) {
|
||||
int i = 0; // index in the list
|
||||
while (strncmp(list[i], "ZZ", 2) &&
|
||||
i < 1000) { // stop at terminator and don't crash without it
|
||||
// test for a match on the sentence name
|
||||
if (!strncmp((const char *)list[i], (const char *)token, strlen(list[i])))
|
||||
return list[i];
|
||||
i++;
|
||||
}
|
||||
return NULL; // couldn't find a match
|
||||
}
|
||||
|
||||
/**************************************************************************/
|
||||
/*!
|
||||
@brief Parse a string token from pointer p to the next comma, asterisk
|
||||
or end of string.
|
||||
@param buff Pointer to the buffer to store the string in
|
||||
@param p Pointer into a string
|
||||
@param n Max permitted size of string including terminating 0
|
||||
@return Pointer to the string buffer
|
||||
*/
|
||||
/**************************************************************************/
|
||||
char *Adafruit_GPS::parseStr(char *buff, char *p, int n) {
|
||||
char *e = strchr(p, ',');
|
||||
int len = 0;
|
||||
if (e) {
|
||||
len = min(e - p, n - 1);
|
||||
strncpy(buff, p, len); // copy up to the comma
|
||||
buff[len] = 0;
|
||||
} else {
|
||||
e = strchr(p, '*');
|
||||
if (e) {
|
||||
len = min(e - p, n - 1);
|
||||
strncpy(buff, p, len); // or up to the *
|
||||
buff[e - p] = 0;
|
||||
} else {
|
||||
len = min((int)strlen(p), n - 1);
|
||||
strncpy(buff, p, len); // or to the end or max capacity
|
||||
}
|
||||
}
|
||||
return buff;
|
||||
}
|
||||
|
||||
/**************************************************************************/
|
||||
/*!
|
||||
@brief Is the field empty, or should we try conversion? Won't work
|
||||
|
|
@ -151,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
|
||||
@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
|
||||
@param p Pointer to the location of the token in the NMEA string
|
||||
*/
|
||||
/**************************************************************************/
|
||||
void Adafruit_GPS::parseLat(char *p) {
|
||||
char degreebuff[10];
|
||||
if (!isEmpty(p)) {
|
||||
strncpy(degreebuff, p, 2);
|
||||
p += 2;
|
||||
degreebuff[2] = '\0';
|
||||
long degree = atol(degreebuff) * 10000000;
|
||||
strncpy(degreebuff, p, 2); // minutes
|
||||
p += 3; // skip decimal point
|
||||
strncpy(degreebuff + 2, p, 4);
|
||||
degreebuff[6] = '\0';
|
||||
long minutes = 50 * atol(degreebuff) / 3;
|
||||
latitude_fixed = degree + minutes;
|
||||
latitude = degree / 100000 + minutes * 0.000006F;
|
||||
latitudeDegrees = (latitude - 100 * int(latitude / 100)) / 60.0f;
|
||||
latitudeDegrees += int(latitude / 100);
|
||||
}
|
||||
}
|
||||
// void Adafruit_GPS::parseLat(char *p) {
|
||||
// char degreebuff[10];
|
||||
// if (!isEmpty(p)) {
|
||||
// strncpy(degreebuff, p, 2);
|
||||
// p += 2;
|
||||
// degreebuff[2] = '\0';
|
||||
// long degree = atol(degreebuff) * 10000000;
|
||||
// strncpy(degreebuff, p, 2); // minutes
|
||||
// p += 3; // skip decimal point
|
||||
// strncpy(degreebuff + 2, p, 4);
|
||||
// degreebuff[6] = '\0';
|
||||
// long minutes = 50 * atol(degreebuff) / 3;
|
||||
// latitude_fixed = degree + minutes;
|
||||
// latitude = degree / 100000 + minutes * 0.000006F;
|
||||
// latitudeDegrees = (latitude - 100 * int(latitude / 100)) / 60.0f;
|
||||
// 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
|
||||
*/
|
||||
/**************************************************************************/
|
||||
bool Adafruit_GPS::parseLatDir(char *p) {
|
||||
if (p[0] == 'S') {
|
||||
lat = 'S';
|
||||
latitudeDegrees *= -1.0f;
|
||||
latitude_fixed *= -1;
|
||||
} else if (p[0] == 'N') {
|
||||
lat = 'N';
|
||||
} else if (p[0] == ',') {
|
||||
lat = 0;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
// bool Adafruit_GPS::parseLatDir(char *p) {
|
||||
// if (p[0] == 'S') {
|
||||
// lat = 'S';
|
||||
// latitudeDegrees *= -1.0f;
|
||||
// latitude_fixed *= -1;
|
||||
// } else if (p[0] == 'N') {
|
||||
// lat = 'N';
|
||||
// } else if (p[0] == ',') {
|
||||
// lat = 0;
|
||||
// } else {
|
||||
// return false;
|
||||
// }
|
||||
// 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
|
||||
*/
|
||||
/**************************************************************************/
|
||||
void Adafruit_GPS::parseLon(char *p) {
|
||||
int32_t degree;
|
||||
long minutes;
|
||||
char degreebuff[10];
|
||||
if (!isEmpty(p)) {
|
||||
strncpy(degreebuff, p, 3);
|
||||
p += 3;
|
||||
degreebuff[3] = '\0';
|
||||
degree = atol(degreebuff) * 10000000;
|
||||
strncpy(degreebuff, p, 2); // minutes
|
||||
p += 3; // skip decimal point
|
||||
strncpy(degreebuff + 2, p, 4);
|
||||
degreebuff[6] = '\0';
|
||||
minutes = 50 * atol(degreebuff) / 3;
|
||||
longitude_fixed = degree + minutes;
|
||||
longitude = degree / 100000 + minutes * 0.000006F;
|
||||
longitudeDegrees = (longitude - 100 * int(longitude / 100)) / 60.0f;
|
||||
longitudeDegrees += int(longitude / 100);
|
||||
}
|
||||
}
|
||||
// void Adafruit_GPS::parseLon(char *p) {
|
||||
// int32_t degree;
|
||||
// long minutes;
|
||||
// char degreebuff[10];
|
||||
// if (!isEmpty(p)) {
|
||||
// strncpy(degreebuff, p, 3);
|
||||
// p += 3;
|
||||
// degreebuff[3] = '\0';
|
||||
// degree = atol(degreebuff) * 10000000;
|
||||
// strncpy(degreebuff, p, 2); // minutes
|
||||
// p += 3; // skip decimal point
|
||||
// strncpy(degreebuff + 2, p, 4);
|
||||
// degreebuff[6] = '\0';
|
||||
// minutes = 50 * atol(degreebuff) / 3;
|
||||
// longitude_fixed = degree + minutes;
|
||||
// longitude = degree / 100000 + minutes * 0.000006F;
|
||||
// longitudeDegrees = (longitude - 100 * int(longitude / 100)) / 60.0f;
|
||||
// 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
|
||||
*/
|
||||
/**************************************************************************/
|
||||
bool Adafruit_GPS::parseLonDir(char *p) {
|
||||
if (!isEmpty(p)) {
|
||||
if (p[0] == 'W') {
|
||||
lon = 'W';
|
||||
longitudeDegrees *= -1.0f;
|
||||
longitude_fixed *= -1;
|
||||
} else if (p[0] == 'E') {
|
||||
lon = 'E';
|
||||
} else if (p[0] == ',') {
|
||||
lon = 0;
|
||||
// bool Adafruit_GPS::parseLonDir(char *p) {
|
||||
// if (!isEmpty(p)) {
|
||||
// if (p[0] == 'W') {
|
||||
// lon = 'W';
|
||||
// longitudeDegrees *= -1.0f;
|
||||
// longitude_fixed *= -1;
|
||||
// } else if (p[0] == 'E') {
|
||||
// lon = 'E';
|
||||
// } else if (p[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 {
|
||||
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;
|
||||
}
|
||||
|
||||
/**************************************************************************/
|
||||
/*!
|
||||
@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
|
||||
@brief Constructor when using SoftwareSerial
|
||||
@param ser Pointer to SoftwareSerial device
|
||||
*/
|
||||
/**************************************************************************/
|
||||
bool Adafruit_GPS::parseFix(char *p) {
|
||||
if (p[0] == 'A') {
|
||||
fix = true;
|
||||
lastFix = sentTime;
|
||||
} else if (p[0] == 'V')
|
||||
fix = false;
|
||||
else
|
||||
return false;
|
||||
return true;
|
||||
#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 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.
|
||||
@brief Constructor when using I2C
|
||||
@param theWire Pointer to an I2C TwoWire object
|
||||
*/
|
||||
/**************************************************************************/
|
||||
nmea_float_t Adafruit_GPS::secondsSinceFix() {
|
||||
return (millis() - lastFix) / 1000.;
|
||||
Adafruit_GPS::Adafruit_GPS(TwoWire *theWire) {
|
||||
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
|
||||
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.
|
||||
@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
|
||||
*/
|
||||
/**************************************************************************/
|
||||
nmea_float_t Adafruit_GPS::secondsSinceTime() {
|
||||
return (millis() - lastTime) / 1000.;
|
||||
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 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.
|
||||
@brief Constructor when there are no communications attached
|
||||
*/
|
||||
/**************************************************************************/
|
||||
nmea_float_t Adafruit_GPS::secondsSinceDate() {
|
||||
return (millis() - lastDate) / 1000.;
|
||||
Adafruit_GPS::Adafruit_GPS() {
|
||||
common_init(); // Set everything to common state, then...
|
||||
noComms = true;
|
||||
}
|
||||
|
||||
/**************************************************************************/
|
||||
/*!
|
||||
@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.
|
||||
@brief Initialization code used by all constructor types
|
||||
*/
|
||||
/**************************************************************************/
|
||||
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
|
||||
char c = 0;
|
||||
|
||||
if (paused)
|
||||
if (paused || noComms)
|
||||
return c;
|
||||
|
||||
#if (defined(__AVR__) || defined(ESP8266)) && defined(USE_SW_SERIAL)
|
||||
|
|
@ -530,121 +454,6 @@ char Adafruit_GPS::read(void) {
|
|||
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
|
||||
|
|
@ -680,28 +489,6 @@ char *Adafruit_GPS::lastNMEA(void) {
|
|||
return (char *)lastline;
|
||||
}
|
||||
|
||||
/**************************************************************************/
|
||||
/*!
|
||||
@brief Parse a hex character and return the appropriate decimal value
|
||||
@param c Hex character, e.g. '0' or 'B'
|
||||
@return Integer value of the hex character. Returns 0 if c is not a proper
|
||||
character
|
||||
*/
|
||||
/**************************************************************************/
|
||||
// read a Hex value and return the decimal equivalent
|
||||
uint8_t Adafruit_GPS::parseHex(char c) {
|
||||
if (c < '0')
|
||||
return 0;
|
||||
if (c <= '9')
|
||||
return c - '0';
|
||||
if (c < 'A')
|
||||
return 0;
|
||||
if (c <= 'F')
|
||||
return (c - 'A') + 10;
|
||||
// if (c > 'F')
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**************************************************************************/
|
||||
/*!
|
||||
@brief Wait for a specified sentence from the device
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -21,8 +21,6 @@
|
|||
*/
|
||||
/**************************************************************************/
|
||||
|
||||
// Fllybob added lines 34,35 and 40,41 to add 100mHz logging capability
|
||||
|
||||
#ifndef _ADAFRUIT_GPS_H
|
||||
#define _ADAFRUIT_GPS_H
|
||||
|
||||
|
|
@ -31,9 +29,15 @@
|
|||
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
|
||||
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
|
||||
#define NMEA_EXTENSIONS ///< if defined will include more NMEA sentences
|
||||
#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
|
||||
///< software serial in the library
|
||||
|
|
@ -60,8 +64,9 @@
|
|||
|
||||
/// type for resulting code from running check()
|
||||
typedef enum {
|
||||
NMEA_BAD = 0, ///< passed none of the checks
|
||||
NMEA_HAS_DOLLAR = 1, ///< has a dollar sign in the first position
|
||||
NMEA_BAD = 0, ///< passed none of the checks
|
||||
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_NAME = 4, ///< there is a token after the $ followed by a comma
|
||||
NMEA_HAS_SOURCE = 10, ///< has a recognized source ID
|
||||
|
|
@ -75,6 +80,7 @@ typedef enum {
|
|||
*/
|
||||
class Adafruit_GPS : public Print {
|
||||
public:
|
||||
// Adafruit_GPS.cpp
|
||||
bool begin(uint32_t baud_or_i2caddr);
|
||||
|
||||
#if (defined(__AVR__) || defined(ESP8266)) && defined(USE_SW_SERIAL)
|
||||
|
|
@ -83,31 +89,60 @@ public:
|
|||
Adafruit_GPS(HardwareSerial *ser); // Constructor when using HardwareSerial
|
||||
Adafruit_GPS(TwoWire *theWire); // Constructor when using I2C
|
||||
Adafruit_GPS(SPIClass *theSPI, int8_t cspin); // Constructor when using SPI
|
||||
|
||||
char *lastNMEA(void);
|
||||
bool newNMEAreceived();
|
||||
Adafruit_GPS(); // Constructor for no communications, just data storage
|
||||
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);
|
||||
|
||||
bool check(char *nmea);
|
||||
bool parse(char *);
|
||||
void addChecksum(char *buff);
|
||||
size_t write(uint8_t);
|
||||
char read(void);
|
||||
void sendCommand(const char *);
|
||||
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 secondsSinceTime();
|
||||
nmea_float_t secondsSinceDate();
|
||||
void resetSentTime();
|
||||
|
||||
bool wakeup(void);
|
||||
bool standby(void);
|
||||
// NMEA_parse.cpp
|
||||
bool parse(char *);
|
||||
bool check(char *nmea);
|
||||
bool onList(char *nmea, const char **list);
|
||||
uint8_t parseHex(char c);
|
||||
|
||||
// NMEA_build.cpp
|
||||
#ifdef NMEA_EXTENSIONS
|
||||
char *build(char *nmea, const char *thisSource, const char *thisSentence,
|
||||
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
|
||||
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 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_records; ///< Log number of data record
|
||||
uint8_t LOCUS_type; ///< Log type, 0: Overlap, 1: FullStop
|
||||
|
|
@ -180,11 +209,20 @@ public:
|
|||
uint8_t LOCUS_percent; ///< Log life used percentage
|
||||
|
||||
#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_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
|
||||
int txtTot = 0; ///< total TXT sentences in group
|
||||
int txtID = 0; ///< id of the text message
|
||||
|
|
@ -192,22 +230,41 @@ public:
|
|||
#endif // NMEA_EXTENSIONS
|
||||
|
||||
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);
|
||||
bool parseCoord(char *p, nmea_float_t *angleDegrees = NULL,
|
||||
nmea_float_t *angle = NULL, int32_t *angle_fixed = NULL,
|
||||
char *dir = NULL);
|
||||
char *parseStr(char *buff, char *p, int n);
|
||||
bool isEmpty(char *pStart);
|
||||
void parseTime(char *);
|
||||
void parseLat(char *);
|
||||
bool parseLatDir(char *);
|
||||
void parseLon(char *);
|
||||
bool parseLonDir(char *);
|
||||
bool parseTime(char *);
|
||||
bool parseFix(char *);
|
||||
bool isEmpty(char *pStart);
|
||||
|
||||
// used by check() for validity tests, room for future expansion
|
||||
const char *sources[5] = {"II", "WI", "GP", "GN",
|
||||
"ZZZ"}; ///< valid source ids
|
||||
const char *sources[6] = {"II", "WI", "GP",
|
||||
"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",
|
||||
"ZZZ"}; ///< parseable sentence ids
|
||||
const char *sentences_known[1] = {
|
||||
"ZZZ"}; ///< known, but not parseable sentence ids
|
||||
const char *sentences_known[4] = {"DBT", "HDM", "HDT",
|
||||
"ZZZ"}; ///< known, but not parseable
|
||||
#endif
|
||||
|
||||
// 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.
|
||||
|
|
@ -226,6 +283,7 @@ private:
|
|||
#if (defined(__AVR__) || defined(ESP8266)) && defined(USE_SW_SERIAL)
|
||||
SoftwareSerial *gpsSwSerial;
|
||||
#endif
|
||||
bool noComms = false;
|
||||
HardwareSerial *gpsHwSerial;
|
||||
TwoWire *gpsI2C;
|
||||
SPIClass *gpsSPI;
|
||||
|
|
|
|||
|
|
@ -45,10 +45,16 @@
|
|||
|
||||
build() will not work properly in an environment that does not support
|
||||
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
|
||||
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
|
||||
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
|
||||
|
|
@ -66,7 +72,8 @@
|
|||
/**************************************************************************/
|
||||
char *Adafruit_GPS::build(char *nmea, const char *thisSource,
|
||||
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"))
|
||||
return NULL;
|
||||
*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
|
||||
// reading.
|
||||
|
||||
if (!strcmp(thisSentence,
|
||||
"GGA")) { //********************************************GGA
|
||||
if (!strcmp(thisSentence, "GGA")) { //************************************GGA
|
||||
// GGA Global Positioning System Fix Data. Time, Position and fix related
|
||||
// data for a GPS receiver
|
||||
// 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
|
||||
// 15) Checksum
|
||||
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,
|
||||
satellites, (double)HDOP, (double)altitude, (double)geoidheight);
|
||||
|
||||
} else if (!strcmp(thisSentence,
|
||||
"GLL")) { //********************************************GLL
|
||||
} else if (!strcmp(thisSentence, "GLL")) { //*****************************GLL
|
||||
// GLL Geographic Position – Latitude/Longitude
|
||||
// 1 2 3 4 5 6 7
|
||||
// | | | | | | |
|
||||
|
|
@ -126,10 +132,10 @@ char *Adafruit_GPS::build(char *nmea, const char *thisSource,
|
|||
// 7) Checksum
|
||||
sprintf(p, "%09.4f,%c,%010.4f,%c,%09.2f,A", (double)latitude, lat,
|
||||
(double)longitude, lon,
|
||||
hour * 10000L + minute * 100L + seconds + milliseconds / 1000.);
|
||||
(double)hour * 10000L + minute * 100L + seconds +
|
||||
milliseconds / 1000.);
|
||||
|
||||
} else if (!strcmp(thisSentence,
|
||||
"GSA")) { //********************************************
|
||||
} else if (!strcmp(thisSentence, "GSA")) { //*****************************GSA
|
||||
// GSA GPS DOP and active satellites
|
||||
// 1 2 3 14 15 16 17 18
|
||||
// | | | | | | | |
|
||||
|
|
@ -146,8 +152,7 @@ char *Adafruit_GPS::build(char *nmea, const char *thisSource,
|
|||
// 18) Checksum
|
||||
return NULL;
|
||||
|
||||
} else if (!strcmp(thisSentence,
|
||||
"RMC")) { //********************************************RMC
|
||||
} else if (!strcmp(thisSentence, "RMC")) { //*****************************RMC
|
||||
// RMC Recommended Minimum Navigation Information
|
||||
// 12
|
||||
// 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
|
||||
// 12) Checksum
|
||||
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)angle, day * 10000 + month * 100 + year,
|
||||
(double)magvariation, mag);
|
||||
|
||||
} else if (!strcmp(thisSentence,
|
||||
"TXT")) { //********************************************TXT
|
||||
} else if (!strcmp(thisSentence, "APB")) { //*****************************APB
|
||||
// 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
|
||||
// TXT Text Transmission
|
||||
// 1 2 3 4 5
|
||||
|
|
@ -185,6 +434,136 @@ char *Adafruit_GPS::build(char *nmea, const char *thisSource,
|
|||
// 5) Checksum
|
||||
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 {
|
||||
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
|
||||
|
||||
/**************************************************************************/
|
||||
/*!
|
||||
@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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -32,112 +32,102 @@
|
|||
|
||||
/**************************************************************************/
|
||||
/*!
|
||||
@brief Parse a NMEA string
|
||||
@brief Parse a standard NMEA string and update the relevant variables.
|
||||
Sentences start with a $, then a two character source identifier, then a
|
||||
three character sentence identifier that defines the format, then a comma and
|
||||
more comma separated fields defined by the sentence name. There are many
|
||||
sentences listed that are not yet supported, including proprietary sentences
|
||||
that start with P, like the $PMTK commands to the GPS modules. See the
|
||||
build() function and http://fort21.ru/download/NMEAdescription.pdf for
|
||||
sentence descriptions.
|
||||
|
||||
Encapsulated data sentences are supported by NMEA-183, and start with !
|
||||
instead of $. https://gpsd.gitlab.io/gpsd/AIVDM.html provides details
|
||||
about encapsulated data sentences used in AIS.
|
||||
|
||||
parse() permits, but does not require Carriage Return and Line Feed at the
|
||||
end of sentences. The end of the sentence is recognized by the * for the
|
||||
checksum. parse() will not recognize a sentence without a valid checksum.
|
||||
|
||||
NMEA_EXTENSIONS must be defined in order to parse more than basic
|
||||
GPS module sentences.
|
||||
|
||||
@param nmea Pointer to the NMEA string
|
||||
@return True if we parsed it, false if it has an invalid checksum or invalid
|
||||
data
|
||||
@return True if successfully parsed, false if fails check or parsing
|
||||
*/
|
||||
/**************************************************************************/
|
||||
bool Adafruit_GPS::parse(char *nmea) {
|
||||
// do checksum check
|
||||
if (!check(nmea))
|
||||
return false;
|
||||
// passed the check, so there's a valid source in thisSource and a valid
|
||||
// sentence in thisSentence
|
||||
|
||||
// look for a few common sentences
|
||||
char *p = nmea; // Pointer to move through the sentence -- good parsers are
|
||||
// non-destructive
|
||||
p = strchr(p, ',') +
|
||||
1; // Skip to the character after the next comma, then check sentence.
|
||||
p = strchr(p, ',') + 1; // Skip to char after the next comma, then check.
|
||||
|
||||
if (!strcmp(thisSentence, "GGA")) {
|
||||
// found GGA
|
||||
// get time
|
||||
// This may look inefficient, but an M0 will get down the list in about 1 us /
|
||||
// strcmp()! Put the GPS sentences from Adafruit_GPS at the top to make
|
||||
// pruning excess code easier. Otherwise, keep them alphabetical for ease of
|
||||
// reading.
|
||||
if (!strcmp(thisSentence, "GGA")) { //************************************GGA
|
||||
// Adafruit from Actisense NGW-1 from SH CP150C
|
||||
parseTime(p);
|
||||
|
||||
// parse out latitude
|
||||
p = strchr(p, ',') + 1; // parse time with specialized function
|
||||
// parse out both latitude and direction, then go to next field, or fail
|
||||
if (parseCoord(p, &latitudeDegrees, &latitude, &latitude_fixed, &lat))
|
||||
newDataValue(NMEA_LAT, latitudeDegrees);
|
||||
p = strchr(p, ',') + 1;
|
||||
parseLat(p);
|
||||
p = strchr(p, ',') + 1;
|
||||
if (!parseLatDir(p))
|
||||
return false;
|
||||
|
||||
// parse out longitude
|
||||
// parse out both longitude and direction, then go to next field, or fail
|
||||
if (parseCoord(p, &longitudeDegrees, &longitude, &longitude_fixed, &lon))
|
||||
newDataValue(NMEA_LON, longitudeDegrees);
|
||||
p = strchr(p, ',') + 1;
|
||||
parseLon(p);
|
||||
p = strchr(p, ',') + 1;
|
||||
if (!parseLonDir(p))
|
||||
return false;
|
||||
|
||||
p = strchr(p, ',') + 1;
|
||||
if (!isEmpty(p)) {
|
||||
fixquality = atoi(p);
|
||||
if (!isEmpty(p)) { // if it's a , (or a * at end of sentence) the value is
|
||||
// not included
|
||||
fixquality = atoi(p); // needs additional processing
|
||||
if (fixquality > 0) {
|
||||
fix = true;
|
||||
lastFix = sentTime;
|
||||
} else
|
||||
fix = false;
|
||||
}
|
||||
|
||||
p = strchr(p, ',') + 1;
|
||||
if (!isEmpty(p)) {
|
||||
p = strchr(p, ',') + 1; // then move on to the next
|
||||
// Most can just be parsed with atoi() or atof(), then move on to the next.
|
||||
if (!isEmpty(p))
|
||||
satellites = atoi(p);
|
||||
}
|
||||
|
||||
p = strchr(p, ',') + 1;
|
||||
if (!isEmpty(p)) {
|
||||
HDOP = atof(p);
|
||||
}
|
||||
|
||||
if (!isEmpty(p))
|
||||
newDataValue(NMEA_HDOP, HDOP = atof(p));
|
||||
p = strchr(p, ',') + 1;
|
||||
if (!isEmpty(p)) {
|
||||
if (!isEmpty(p))
|
||||
altitude = atof(p);
|
||||
}
|
||||
|
||||
p = strchr(p, ',') + 1;
|
||||
p = strchr(p, ',') + 1;
|
||||
if (!isEmpty(p)) {
|
||||
geoidheight = atof(p);
|
||||
}
|
||||
}
|
||||
p = strchr(p, ',') + 1; // skip the units
|
||||
if (!isEmpty(p))
|
||||
geoidheight = atof(p); // skip the rest
|
||||
|
||||
else if (!strcmp(thisSentence, "RMC")) {
|
||||
// found RMC
|
||||
// get time
|
||||
} else if (!strcmp(thisSentence, "RMC")) { //*****************************RMC
|
||||
// in Adafruit from Actisense NGW-1 from SH CP150C
|
||||
parseTime(p);
|
||||
|
||||
// fix or no fix
|
||||
p = strchr(p, ',') + 1;
|
||||
if (!parseFix(p))
|
||||
return false;
|
||||
|
||||
// parse out latitude
|
||||
parseFix(p);
|
||||
p = strchr(p, ',') + 1;
|
||||
parseLat(p);
|
||||
// parse out both latitude and direction, then go to next field, or fail
|
||||
if (parseCoord(p, &latitudeDegrees, &latitude, &latitude_fixed, &lat))
|
||||
newDataValue(NMEA_LAT, latitudeDegrees);
|
||||
p = strchr(p, ',') + 1;
|
||||
if (!parseLatDir(p))
|
||||
return false;
|
||||
|
||||
// parse out longitude
|
||||
p = strchr(p, ',') + 1;
|
||||
parseLon(p);
|
||||
// parse out both longitude and direction, then go to next field, or fail
|
||||
if (parseCoord(p, &longitudeDegrees, &longitude, &longitude_fixed, &lon))
|
||||
newDataValue(NMEA_LON, longitudeDegrees);
|
||||
p = strchr(p, ',') + 1;
|
||||
if (!parseLonDir(p))
|
||||
return false;
|
||||
|
||||
// speed
|
||||
p = strchr(p, ',') + 1;
|
||||
if (!isEmpty(p)) {
|
||||
speed = atof(p);
|
||||
}
|
||||
|
||||
// angle
|
||||
if (!isEmpty(p))
|
||||
newDataValue(NMEA_SOG, speed = atof(p));
|
||||
p = strchr(p, ',') + 1;
|
||||
if (!isEmpty(p)) {
|
||||
angle = atof(p);
|
||||
}
|
||||
|
||||
if (!isEmpty(p))
|
||||
newDataValue(NMEA_COG, angle = atof(p));
|
||||
p = strchr(p, ',') + 1;
|
||||
if (!isEmpty(p)) {
|
||||
uint32_t fulldate = atof(p);
|
||||
|
|
@ -145,66 +135,264 @@ bool Adafruit_GPS::parse(char *nmea) {
|
|||
month = (fulldate % 10000) / 100;
|
||||
year = (fulldate % 100);
|
||||
lastDate = sentTime;
|
||||
}
|
||||
}
|
||||
} // skip the rest
|
||||
|
||||
else if (!strcmp(thisSentence, "GLL")) {
|
||||
// found GLL
|
||||
// parse out latitude
|
||||
parseLat(p);
|
||||
} else if (!strcmp(thisSentence, "GLL")) { //*****************************GLL
|
||||
// in Adafruit from Actisense NGW-1 from SH CP150C
|
||||
// parse out both latitude and direction, then go to next field, or fail
|
||||
if (parseCoord(p, &latitudeDegrees, &latitude, &latitude_fixed, &lat))
|
||||
newDataValue(NMEA_LAT, latitudeDegrees);
|
||||
p = strchr(p, ',') + 1;
|
||||
if (!parseLatDir(p))
|
||||
return false;
|
||||
|
||||
// parse out longitude
|
||||
p = strchr(p, ',') + 1;
|
||||
parseLon(p);
|
||||
// parse out both longitude and direction, then go to next field, or fail
|
||||
if (parseCoord(p, &longitudeDegrees, &longitude, &longitude_fixed, &lon))
|
||||
newDataValue(NMEA_LON, longitudeDegrees);
|
||||
p = strchr(p, ',') + 1;
|
||||
if (!parseLonDir(p))
|
||||
return false;
|
||||
|
||||
// get time
|
||||
p = strchr(p, ',') + 1;
|
||||
parseTime(p);
|
||||
|
||||
// fix or no fix
|
||||
p = strchr(p, ',') + 1;
|
||||
if (!parseFix(p))
|
||||
return false;
|
||||
}
|
||||
parseFix(p); // skip the rest
|
||||
|
||||
else if (!strcmp(thisSentence, "GSA")) {
|
||||
// found GSA
|
||||
// parse out Auto selection, but ignore them
|
||||
// parse out 3d fixquality
|
||||
p = strchr(p, ',') + 1;
|
||||
if (!isEmpty(p)) {
|
||||
} else if (!strcmp(thisSentence, "GSA")) { //*****************************GSA
|
||||
// in Adafruit from Actisense NGW-1
|
||||
p = strchr(p, ',') + 1; // skip selection mode
|
||||
if (!isEmpty(p))
|
||||
fixquality_3d = atoi(p);
|
||||
}
|
||||
p = strchr(p, ',') + 1;
|
||||
// skip 12 Satellite PDNs without interpreting them
|
||||
for (int i = 0; i < 12; i++)
|
||||
p = strchr(p, ',') + 1;
|
||||
|
||||
// parse out PDOP
|
||||
p = strchr(p, ',') + 1;
|
||||
if (!isEmpty(p)) {
|
||||
if (!isEmpty(p))
|
||||
PDOP = atof(p);
|
||||
}
|
||||
p = strchr(p, ',') + 1;
|
||||
// parse out HDOP, we also parse this from the GGA sentence. Chipset should
|
||||
// report the same for both
|
||||
if (!isEmpty(p))
|
||||
newDataValue(NMEA_HDOP, HDOP = atof(p));
|
||||
p = strchr(p, ',') + 1;
|
||||
if (!isEmpty(p)) {
|
||||
HDOP = atof(p);
|
||||
}
|
||||
// parse out VDOP
|
||||
p = strchr(p, ',') + 1;
|
||||
if (!isEmpty(p)) {
|
||||
VDOP = atof(p);
|
||||
}
|
||||
}
|
||||
if (!isEmpty(p))
|
||||
VDOP = atof(p); // last before checksum
|
||||
|
||||
}
|
||||
#ifdef NMEA_EXTENSIONS // Sentences not required for basic GPS functionality
|
||||
else if (!strcmp(thisSentence, "TXT")) { //*******************************TXT
|
||||
else if (!strcmp(thisSentence, "APB")) { //*******************************APB
|
||||
// from Actisense NGW-1 from SH CP150C
|
||||
return false;
|
||||
|
||||
} else if (!strcmp(thisSentence, "DBT")) { //*****************************DBT
|
||||
// from Actisense NGW-1
|
||||
// feet, metres, fathoms below transducer coerced to water depth from
|
||||
// surface in metres
|
||||
if (!isEmpty(p))
|
||||
newDataValue(NMEA_DEPTH, atof(p) * 0.3048 + depthToTransducer);
|
||||
p = strchr(p, ',') + 1;
|
||||
p = strchr(p, ',') + 1;
|
||||
if (!isEmpty(p))
|
||||
newDataValue(NMEA_DEPTH, atof(p) + depthToTransducer);
|
||||
p = strchr(p, ',') + 1;
|
||||
p = strchr(p, ',') + 1;
|
||||
if (!isEmpty(p))
|
||||
newDataValue(NMEA_DEPTH, atof(p) * 6 * 0.3048 + depthToTransducer);
|
||||
|
||||
} else if (!strcmp(thisSentence, "DPT")) { //*****************************DPT
|
||||
// from Actisense NGW-1
|
||||
return false;
|
||||
|
||||
} else if (!strcmp(thisSentence, "GSV")) { //*****************************GSV
|
||||
// from Actisense NGW-1
|
||||
return false;
|
||||
|
||||
} else if (!strcmp(thisSentence, "HDG")) { //*****************************HDG
|
||||
// from Actisense NGW-1 from SH CP150C
|
||||
return false;
|
||||
|
||||
} else if (!strcmp(thisSentence, "HDM")) { //*****************************HDM
|
||||
if (!isEmpty(p))
|
||||
newDataValue(NMEA_HDG, atof(p)); // skip the rest
|
||||
|
||||
} else if (!strcmp(thisSentence, "HDT")) { //*****************************HDT
|
||||
if (!isEmpty(p))
|
||||
newDataValue(NMEA_HDT, atof(p)); // skip the rest
|
||||
|
||||
} else if (!strcmp(thisSentence, "MDA")) { //*****************************MDA
|
||||
// from Actisense NGW-1
|
||||
if (!isEmpty(p))
|
||||
newDataValue(NMEA_BAROMETER, atof(p) * 3386.39);
|
||||
p = strchr(p, ',') + 1;
|
||||
p = strchr(p, ',') + 1;
|
||||
if (!isEmpty(p))
|
||||
newDataValue(NMEA_BAROMETER, atof(p) * 100000);
|
||||
p = strchr(p, ',') + 1;
|
||||
p = strchr(p, ',') + 1;
|
||||
nmea_float_t T = 100000.;
|
||||
char u = 'C';
|
||||
if (!isEmpty(p))
|
||||
T = atof(p);
|
||||
p = strchr(p, ',') + 1;
|
||||
if (!isEmpty(p))
|
||||
u = *p;
|
||||
p = strchr(p, ',') + 1;
|
||||
if (u != 'C') {
|
||||
T = (T - 32) / 1.8;
|
||||
u = 'C';
|
||||
} // coerce to C
|
||||
if (T < 1000)
|
||||
newDataValue(NMEA_TEMPERATURE_AIR, T);
|
||||
T = 100000.;
|
||||
u = 'C';
|
||||
if (!isEmpty(p))
|
||||
T = atof(p);
|
||||
p = strchr(p, ',') + 1;
|
||||
if (!isEmpty(p))
|
||||
u = *p;
|
||||
p = strchr(p, ',') + 1;
|
||||
if (u != 'C') {
|
||||
T = (T - 32) / 1.8;
|
||||
u = 'C';
|
||||
}
|
||||
if (T < 1000)
|
||||
newDataValue(NMEA_TEMPERATURE_WATER, T);
|
||||
if (!isEmpty(p))
|
||||
newDataValue(NMEA_HUMIDITY, atof(p)); // skip the rest
|
||||
|
||||
} else if (!strcmp(thisSentence, "MTW")) { //*****************************MTW
|
||||
nmea_float_t T = 100000.;
|
||||
char u = 'C';
|
||||
if (!isEmpty(p))
|
||||
T = atof(p);
|
||||
p = strchr(p, ',') + 1;
|
||||
if (!isEmpty(p))
|
||||
u = *p; // last before checksum
|
||||
if (u != 'C') {
|
||||
T = (T - 32) / 1.8;
|
||||
u = 'C';
|
||||
}
|
||||
if (T < 1000)
|
||||
newDataValue(NMEA_TEMPERATURE_WATER, T);
|
||||
|
||||
} else if (!strcmp(thisSentence, "MWD")) { //*****************************MWD
|
||||
// from Actisense NGW-1
|
||||
return false;
|
||||
|
||||
} else if (!strcmp(thisSentence, "MWV")) { //*****************************MWV
|
||||
// from Actisense NGW-1
|
||||
nmea_float_t ang = 100000.;
|
||||
char ref = 'T';
|
||||
if (!isEmpty(p))
|
||||
ang = atof(p);
|
||||
p = strchr(p, ',') + 1;
|
||||
if (!isEmpty(p))
|
||||
ref = *p;
|
||||
p = strchr(p, ',') + 1;
|
||||
nmea_float_t spd = 100000.;
|
||||
if (!isEmpty(p))
|
||||
spd = atof(p);
|
||||
p = strchr(p, ',') + 1;
|
||||
char units = 'N';
|
||||
if (!isEmpty(p))
|
||||
units = *p;
|
||||
p = strchr(p, ',') + 1;
|
||||
char stat = 'A';
|
||||
if (!isEmpty(p))
|
||||
stat = *p; // last before checksum
|
||||
if (units == 'K') {
|
||||
spd /= 1.6;
|
||||
units = 'M';
|
||||
}
|
||||
if (units == 'M') {
|
||||
spd *= 5280. / 6000.;
|
||||
units = 'N';
|
||||
}
|
||||
if (ang > 180.)
|
||||
ang -= 360.;
|
||||
if (ref == 'R') {
|
||||
if (ang < 1000. && stat == 'A')
|
||||
newDataValue(NMEA_AWA, ang);
|
||||
if (spd < 1000. && stat == 'A')
|
||||
newDataValue(NMEA_AWS, spd);
|
||||
} else {
|
||||
if (ang < 1000. && stat == 'A')
|
||||
newDataValue(NMEA_TWA, ang);
|
||||
if (spd < 1000. && stat == 'A')
|
||||
newDataValue(NMEA_TWS, spd);
|
||||
}
|
||||
|
||||
} else if (!strcmp(thisSentence, "RMB")) { //*****************************RMB
|
||||
// from Actisense NGW-1 from SH CP150C
|
||||
p = strchr(p, ',') + 1; // skip status
|
||||
nmea_float_t xte = 100000.;
|
||||
char xteDir = 'X';
|
||||
if (!isEmpty(p))
|
||||
xte = atof(p);
|
||||
p = strchr(p, ',') + 1;
|
||||
if (!isEmpty(p))
|
||||
xteDir = *p;
|
||||
p = strchr(p, ',') + 1;
|
||||
if (xte < 10000. && xteDir != 'X') {
|
||||
if (xteDir == 'L')
|
||||
xte *= -1.;
|
||||
newDataValue(NMEA_XTE, xte);
|
||||
}
|
||||
if (!isEmpty(p))
|
||||
parseStr(toID, p, NMEA_MAX_WP_ID);
|
||||
p = strchr(p, ',') + 1;
|
||||
if (!isEmpty(p))
|
||||
parseStr(fromID, p, NMEA_MAX_WP_ID);
|
||||
p = strchr(p, ',') + 1;
|
||||
nmea_float_t latitudeWP =
|
||||
0; // All the same position data for the next way point
|
||||
nmea_float_t longitudeWP = 0;
|
||||
int32_t latitude_fixedWP = 0;
|
||||
int32_t longitude_fixedWP = 0;
|
||||
nmea_float_t latitudeDegreesWP = 0;
|
||||
nmea_float_t longitudeDegreesWP = 0;
|
||||
char latWP = 'N';
|
||||
char lonWP = 'W';
|
||||
|
||||
// parse out both latitude and direction for WayPoint, then go to next
|
||||
// field, or fail
|
||||
if (!isEmpty(p)) {
|
||||
if (!parseCoord(p, &latitudeDegreesWP, &latitudeWP, &latitude_fixedWP,
|
||||
&latWP))
|
||||
return false;
|
||||
else
|
||||
newDataValue(NMEA_LATWP, latitudeDegreesWP);
|
||||
}
|
||||
p = strchr(p, ',') + 1;
|
||||
p = strchr(p, ',') + 1;
|
||||
// parse out both longitude and direction for WayPoint, then go to next
|
||||
// field, or fail
|
||||
if (!isEmpty(p)) {
|
||||
if (!parseCoord(p, &longitudeDegreesWP, &longitudeWP, &longitude_fixedWP,
|
||||
&lonWP))
|
||||
return false;
|
||||
else
|
||||
newDataValue(NMEA_LONWP, longitudeDegreesWP);
|
||||
}
|
||||
p = strchr(p, ',') + 1;
|
||||
p = strchr(p, ',') + 1;
|
||||
p = strchr(p, ',') + 1;
|
||||
p = strchr(p, ',') + 1;
|
||||
if (!isEmpty(p))
|
||||
newDataValue(NMEA_DISTWP, atof(p));
|
||||
p = strchr(p, ',') + 1;
|
||||
if (!isEmpty(p))
|
||||
newDataValue(NMEA_COGWP, atof(p));
|
||||
p = strchr(p, ',') + 1;
|
||||
if (!isEmpty(p))
|
||||
newDataValue(NMEA_VMGWP, atof(p)); // skip arrival flag
|
||||
|
||||
} else if (!strcmp(thisSentence, "ROT")) { //*****************************ROT
|
||||
return false;
|
||||
|
||||
} else if (!strcmp(thisSentence, "RPM")) { //*****************************RPM
|
||||
return false;
|
||||
|
||||
} else if (!strcmp(thisSentence, "RSA")) { //*****************************RSA
|
||||
// from Actisense NGW-1
|
||||
return false;
|
||||
|
||||
} else if (!strcmp(thisSentence, "TXT")) { //*****************************TXT
|
||||
if (!isEmpty(p))
|
||||
txtTot = atoi(p);
|
||||
p = strchr(p, ',') + 1;
|
||||
|
|
@ -216,12 +404,128 @@ bool Adafruit_GPS::parse(char *nmea) {
|
|||
p = strchr(p, ',') + 1;
|
||||
if (!isEmpty(p))
|
||||
parseStr(txtTXT, p, 61); // copy the text to NMEA TXT max of 61 characters
|
||||
|
||||
} else if (!strcmp(thisSentence, "VDR")) { //*****************************VDR
|
||||
// from Actisense NGW-1
|
||||
return false;
|
||||
|
||||
} else if (!strcmp(thisSentence, "VHW")) { //*****************************VHW
|
||||
// from Actisense NGW-1
|
||||
if (!isEmpty(p))
|
||||
newDataValue(NMEA_HDT, atof(p));
|
||||
p = strchr(p, ',') + 1;
|
||||
p = strchr(p, ',') + 1;
|
||||
if (!isEmpty(p))
|
||||
newDataValue(NMEA_HDG, atof(p));
|
||||
p = strchr(p, ',') + 1;
|
||||
p = strchr(p, ',') + 1;
|
||||
if (!isEmpty(p))
|
||||
newDataValue(NMEA_VTW, atof(p)); // skip the other units
|
||||
|
||||
} else if (!strcmp(thisSentence, "VLW")) { //*****************************VLW
|
||||
// from Actisense NGW-1
|
||||
if (!isEmpty(p))
|
||||
newDataValue(NMEA_LOG, atof(p));
|
||||
p = strchr(p, ',') + 1;
|
||||
p = strchr(p, ',') + 1;
|
||||
if (!isEmpty(p))
|
||||
newDataValue(NMEA_LOGR, atof(p)); // skip the other units
|
||||
|
||||
} else if (!strcmp(thisSentence, "VPW")) { //*****************************VPW
|
||||
// knots, metres/s coerced to knots
|
||||
nmea_float_t vmg = 100000.;
|
||||
if (!isEmpty(p))
|
||||
vmg = atof(p);
|
||||
p = strchr(p, ',') + 1;
|
||||
p = strchr(p, ',') + 1;
|
||||
if (!isEmpty(p))
|
||||
vmg = atof(p) * 0.3048 * 3600. / 6000.; // skip units
|
||||
if (vmg < 1000.)
|
||||
newDataValue(NMEA_VMG, vmg);
|
||||
} else if (!strcmp(thisSentence, "VTG")) { //*****************************VTG
|
||||
// from Actisense NGW-1 from SH CP150C
|
||||
return false;
|
||||
|
||||
} else if (!strcmp(thisSentence, "VWR")) { //*****************************VWR
|
||||
// from Actisense NGW-1
|
||||
nmea_float_t ang = 1000.;
|
||||
if (!isEmpty(p))
|
||||
ang = atof(p);
|
||||
p = strchr(p, ',') + 1;
|
||||
char ref = ' ';
|
||||
if (!isEmpty(p))
|
||||
ref = *p;
|
||||
p = strchr(p, ',') + 1;
|
||||
if (ref == 'L')
|
||||
ang *= -1;
|
||||
if (ang < 1000.)
|
||||
newDataValue(NMEA_AWA, ang);
|
||||
nmea_float_t ws = 0.0;
|
||||
char units = 'X';
|
||||
if (!isEmpty(p))
|
||||
ws = atof(p);
|
||||
p = strchr(p, ',') + 1; // knots
|
||||
if (!isEmpty(p))
|
||||
units = *p;
|
||||
p = strchr(p, ',') + 1;
|
||||
if (!isEmpty(p))
|
||||
ws = atof(p);
|
||||
p = strchr(p, ',') + 1; // meters / second
|
||||
if (!isEmpty(p))
|
||||
units = *p;
|
||||
p = strchr(p, ',') + 1; // M
|
||||
if (!isEmpty(p))
|
||||
ws = atof(p);
|
||||
p = strchr(p, ',') + 1; // kilometers / hour can be converted back to knots
|
||||
if (!isEmpty(p))
|
||||
units = *p; // last before checksum
|
||||
if (units == 'M') {
|
||||
ws *= 3.6;
|
||||
units = 'K';
|
||||
} // convert m/s to km/h
|
||||
if (units == 'K') {
|
||||
ws /= 1.6;
|
||||
units = 'M';
|
||||
} // convert km/h to miles / h
|
||||
if (units == 'M') {
|
||||
ws *= 5280. / 6000.;
|
||||
units = 'N';
|
||||
} // convert miles / hr to knots
|
||||
if (units == 'N')
|
||||
newDataValue(NMEA_AWS, ws); // store the final result
|
||||
|
||||
} else if (!strcmp(thisSentence, "WCV")) { //*****************************WCV
|
||||
// from SH CP150C
|
||||
if (!isEmpty(p))
|
||||
newDataValue(NMEA_VMGWP, atof(p)); // skip the rest
|
||||
|
||||
} else if (!strcmp(thisSentence, "XTE")) { //*****************************XTE
|
||||
// from Actisense NGW-1 from SH CP150C
|
||||
p = strchr(p, ',') + 1; // skip status 1
|
||||
p = strchr(p, ',') + 1; // skip status 2
|
||||
nmea_float_t xte = 100000.;
|
||||
char xteDir = 'X';
|
||||
if (!isEmpty(p))
|
||||
xte = atof(p);
|
||||
p = strchr(p, ',') + 1;
|
||||
if (!isEmpty(p))
|
||||
xteDir = *p;
|
||||
p = strchr(p, ',') + 1;
|
||||
if (xte < 10000. && xteDir != 'X') {
|
||||
if (xteDir == 'L')
|
||||
xte *= -1.;
|
||||
newDataValue(NMEA_XTE, xte);
|
||||
} // skip units
|
||||
|
||||
} else if (!strcmp(thisSentence, "ZDA")) { //*****************************ZDA
|
||||
// from Actisense NGW-1
|
||||
return false;
|
||||
}
|
||||
#endif // NMEA_EXTENSIONS
|
||||
|
||||
// we dont parse the remaining, yet!
|
||||
else
|
||||
return false;
|
||||
else {
|
||||
return false; // didn't find the required sentence definition
|
||||
}
|
||||
|
||||
// Record the successful parsing of where the last data came from and when
|
||||
strcpy(lastSource, thisSource);
|
||||
|
|
@ -229,3 +533,300 @@ bool Adafruit_GPS::parse(char *nmea) {
|
|||
lastUpdate = millis();
|
||||
return true;
|
||||
}
|
||||
|
||||
/**************************************************************************/
|
||||
/*!
|
||||
@brief Check an NMEA string for basic format, valid source ID and valid
|
||||
and valid sentence ID. Update the values of thisCheck, thisSource and
|
||||
thisSentence.
|
||||
@param nmea Pointer to the NMEA string
|
||||
@return True if well formed, false if it has problems
|
||||
*/
|
||||
/**************************************************************************/
|
||||
boolean Adafruit_GPS::check(char *nmea) {
|
||||
thisCheck = 0; // new check
|
||||
if (*nmea != '$' && *nmea != '!')
|
||||
return false; // doesn't start with $ or !
|
||||
else
|
||||
thisCheck += NMEA_HAS_DOLLAR;
|
||||
// do checksum check -- first look if we even have one -- ignore all but last
|
||||
// *
|
||||
char *ast = nmea; // not strchr(nmea,'*'); for first *
|
||||
while (*ast)
|
||||
ast++; // go to the end
|
||||
while (*ast != '*' && ast > nmea)
|
||||
ast--; // then back to * if it's there
|
||||
if (*ast != '*')
|
||||
return false; // there is no asterisk
|
||||
else {
|
||||
uint16_t sum = parseHex(*(ast + 1)) * 16; // extract checksum
|
||||
sum += parseHex(*(ast + 2));
|
||||
char *p = nmea; // check checksum
|
||||
for (char *p1 = p + 1; p1 < ast; p1++)
|
||||
sum ^= *p1;
|
||||
if (sum != 0)
|
||||
return false; // bad checksum :(
|
||||
else
|
||||
thisCheck += NMEA_HAS_CHECKSUM;
|
||||
}
|
||||
// extract source of variable length
|
||||
char *p = nmea + 1;
|
||||
const char *src = tokenOnList(p, sources);
|
||||
if (src) {
|
||||
strcpy(thisSource, src);
|
||||
thisCheck += NMEA_HAS_SOURCE;
|
||||
} else
|
||||
return false;
|
||||
p += strlen(src);
|
||||
// extract sentence id and check if parsed
|
||||
const char *snc = tokenOnList(p, sentences_parsed);
|
||||
if (snc) {
|
||||
strcpy(thisSentence, snc);
|
||||
thisCheck += NMEA_HAS_SENTENCE_P + NMEA_HAS_SENTENCE;
|
||||
} else { // check if known
|
||||
snc = tokenOnList(p, sentences_known);
|
||||
if (snc) {
|
||||
strcpy(thisSentence, snc);
|
||||
thisCheck += NMEA_HAS_SENTENCE;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true; // passed all the tests
|
||||
}
|
||||
|
||||
/**************************************************************************/
|
||||
/*!
|
||||
@brief Check if a token at the start of a string is on a list.
|
||||
@param token Pointer to the string
|
||||
@param list A list of strings, with the final entry starting "ZZ"
|
||||
@return Pointer to the found token, or NULL if it fails
|
||||
*/
|
||||
/**************************************************************************/
|
||||
const char *Adafruit_GPS::tokenOnList(char *token, const char **list) {
|
||||
int i = 0; // index in the list
|
||||
while (strncmp(list[i], "ZZ", 2) &&
|
||||
i < 1000) { // stop at terminator and don't crash without it
|
||||
// test for a match on the sentence name
|
||||
if (!strncmp((const char *)list[i], (const char *)token, strlen(list[i])))
|
||||
return list[i];
|
||||
i++;
|
||||
}
|
||||
return NULL; // couldn't find a match
|
||||
}
|
||||
|
||||
/**************************************************************************/
|
||||
/*!
|
||||
@brief Check if an NMEA string is valid and is on a list, perhaps to
|
||||
decide if it should be passed to a particular NMEA device.
|
||||
@param nmea Pointer to the NMEA string
|
||||
@param list A list of strings, with the final entry "ZZ"
|
||||
@return True if on the list, false if it fails check or is not on the list
|
||||
*/
|
||||
/**************************************************************************/
|
||||
bool Adafruit_GPS::onList(char *nmea, const char **list) {
|
||||
if (!check(nmea)) // sets thisSentence if valid
|
||||
return false; // not a valid sentence
|
||||
// stop at terminator with first two letters ZZ and don't crash without it
|
||||
for (int i = 0; strncmp(list[i], "ZZ", 2) && i < 1000; i++) {
|
||||
// test for a match on the sentence name
|
||||
if (!strcmp((const char *)list[i], (const char *)thisSentence))
|
||||
return true;
|
||||
}
|
||||
return false; // couldn't find a match
|
||||
}
|
||||
|
||||
/**************************************************************************/
|
||||
/*!
|
||||
@brief Parse a part of an NMEA string for lat or lon angle and direction.
|
||||
Works for either DDMM.mmmm,N (latitude) or DDDMM.mmmm,W (longitude) format.
|
||||
Insensitive to number of decimal places present. Only fills the variables
|
||||
if it succeeds and the variable pointer is not NULL. This allows calling
|
||||
to fill only the variables of interest. Does rudimentary validation on
|
||||
angle range.
|
||||
|
||||
Supersedes private functions parseLat(), parseLon(), parseLatDir(),
|
||||
parseLonDir(), all previously called from parse().
|
||||
@param pStart Pointer to the location of the token in the NMEA string
|
||||
@param angle Pointer to the angle to fill with value in degrees/minutes as
|
||||
received from the GPS (DDDMM.MMMM), unsigned
|
||||
@param angle_fixed Pointer to the fix point version latitude in decimal
|
||||
degrees * 10000000, signed
|
||||
@param angleDegrees Pointer to the angle to fill with decimal degrees,
|
||||
signed. As actual double on SAMD, etc. resolution is better than the
|
||||
fixed point version.
|
||||
@param dir Pointer to character to fill the direction N/S/E/W
|
||||
@return true if successful, false if failed or no value
|
||||
*/
|
||||
/**************************************************************************/
|
||||
bool Adafruit_GPS::parseCoord(char *pStart, nmea_float_t *angleDegrees,
|
||||
nmea_float_t *angle, int32_t *angle_fixed,
|
||||
char *dir) {
|
||||
char *p = pStart;
|
||||
if (!isEmpty(p)) {
|
||||
// get the number in DDDMM.mmmm format and break into components
|
||||
char degreebuff[10];
|
||||
char *e = strchr(p, '.');
|
||||
if (e == NULL || e - p > 6)
|
||||
return false; // no decimal point in range
|
||||
strncpy(degreebuff, p, e - p); // get DDDMM
|
||||
long dddmm = atol(degreebuff);
|
||||
long degrees = (dddmm / 100); // truncate the minutes
|
||||
long minutes = dddmm - degrees * 100; // remove the degrees
|
||||
p = e; // start from the decimal point
|
||||
nmea_float_t decminutes = atof(e); // the fraction after the decimal point
|
||||
p = strchr(p, ',') + 1; // go to the next field
|
||||
|
||||
// get the NSEW direction as a character
|
||||
char nsew = 'X';
|
||||
if (!isEmpty(p))
|
||||
nsew = *p; // field is not empty
|
||||
else
|
||||
return false; // no direction provided
|
||||
|
||||
// set the various numerical formats to their values
|
||||
long fixed = degrees * 10000000 + (minutes * 10000000) / 60 +
|
||||
(decminutes * 10000000) / 60;
|
||||
nmea_float_t ang = degrees * 100 + minutes + decminutes;
|
||||
nmea_float_t deg = fixed / (nmea_float_t)10000000.;
|
||||
if (nsew == 'S' ||
|
||||
nsew == 'W') { // fixed and deg are signed, but DDDMM.mmmm is not
|
||||
fixed = -fixed;
|
||||
deg = -deg;
|
||||
}
|
||||
|
||||
// reject directions that are not NSEW
|
||||
if (nsew != 'N' && nsew != 'S' && nsew != 'E' && nsew != 'W')
|
||||
return false;
|
||||
|
||||
// reject angles that are out of range
|
||||
if (nsew == 'N' || nsew == 'S')
|
||||
if (abs(deg) > 90)
|
||||
return false;
|
||||
if (abs(deg) > 180)
|
||||
return false;
|
||||
|
||||
// store in locations passed as args
|
||||
if (angle != NULL)
|
||||
*angle = ang;
|
||||
if (angle_fixed != NULL)
|
||||
*angle_fixed = fixed;
|
||||
if (angleDegrees != NULL)
|
||||
*angleDegrees = deg;
|
||||
if (dir != NULL)
|
||||
*dir = nsew;
|
||||
} else
|
||||
return false; // no number
|
||||
return true;
|
||||
}
|
||||
|
||||
/**************************************************************************/
|
||||
/*!
|
||||
@brief Parse a string token from pointer p to the next comma, asterisk
|
||||
or end of string.
|
||||
@param buff Pointer to the buffer to store the string in
|
||||
@param p Pointer into a string
|
||||
@param n Max permitted size of string including terminating 0
|
||||
@return Pointer to the string buffer
|
||||
*/
|
||||
/**************************************************************************/
|
||||
char *Adafruit_GPS::parseStr(char *buff, char *p, int n) {
|
||||
char *e = strchr(p, ',');
|
||||
int len = 0;
|
||||
if (e) {
|
||||
len = min(e - p, n - 1);
|
||||
strncpy(buff, p, len); // copy up to the comma
|
||||
buff[len] = 0;
|
||||
} else {
|
||||
e = strchr(p, '*');
|
||||
if (e) {
|
||||
len = min(e - p, n - 1);
|
||||
strncpy(buff, p, len); // or up to the *
|
||||
buff[e - p] = 0;
|
||||
} else {
|
||||
len = min((int)strlen(p), n - 1);
|
||||
strncpy(buff, p, len); // or to the end or max capacity
|
||||
}
|
||||
}
|
||||
return buff;
|
||||
}
|
||||
|
||||
/**************************************************************************/
|
||||
/*!
|
||||
@brief Parse a part of an NMEA string for time. Independent of number
|
||||
of decimal places after the '.'
|
||||
@param p Pointer to the location of the token in the NMEA string
|
||||
@return true if successful, false otherwise
|
||||
*/
|
||||
/**************************************************************************/
|
||||
bool Adafruit_GPS::parseTime(char *p) {
|
||||
if (!isEmpty(p)) { // get time
|
||||
uint32_t time = atol(p);
|
||||
hour = time / 10000;
|
||||
minute = (time % 10000) / 100;
|
||||
seconds = (time % 100);
|
||||
p = strchr(p, '.');
|
||||
milliseconds = atof(p) * 1000;
|
||||
lastTime = sentTime;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**************************************************************************/
|
||||
/*!
|
||||
@brief Parse a part of an NMEA string for whether there is a fix
|
||||
@param p Pointer to the location of the token in the NMEA string
|
||||
@return True if we parsed it, false if it has invalid data
|
||||
*/
|
||||
/**************************************************************************/
|
||||
boolean Adafruit_GPS::parseFix(char *p) {
|
||||
if (!isEmpty(p)) {
|
||||
if (p[0] == 'A') {
|
||||
fix = true;
|
||||
lastFix = sentTime;
|
||||
} else if (p[0] == 'V')
|
||||
fix = false;
|
||||
else
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**************************************************************************/
|
||||
/*!
|
||||
@brief Is the field empty, or should we try conversion? Won't work
|
||||
for a text field that starts with an asterisk or a comma, but that
|
||||
probably violates the NMEA-183 standard.
|
||||
@param pStart Pointer to the location of the token in the NMEA string
|
||||
@return true if empty field, false if something there
|
||||
*/
|
||||
/**************************************************************************/
|
||||
bool Adafruit_GPS::isEmpty(char *pStart) {
|
||||
if (',' != *pStart && '*' != *pStart && pStart != NULL)
|
||||
return false;
|
||||
else
|
||||
return true;
|
||||
}
|
||||
|
||||
/**************************************************************************/
|
||||
/*!
|
||||
@brief Parse a hex character and return the appropriate decimal value
|
||||
@param c Hex character, e.g. '0' or 'B'
|
||||
@return Integer value of the hex character. Returns 0 if c is not a proper
|
||||
character
|
||||
*/
|
||||
/**************************************************************************/
|
||||
// read a Hex value and return the decimal equivalent
|
||||
uint8_t Adafruit_GPS::parseHex(char c) {
|
||||
if (c < '0')
|
||||
return 0;
|
||||
if (c <= '9')
|
||||
return c - '0';
|
||||
if (c < 'A')
|
||||
return 0;
|
||||
if (c <= 'F')
|
||||
return (c - 'A') + 10;
|
||||
// if (c > 'F')
|
||||
return 0;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue