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
|
#ifdef NMEA_EXTENSIONS
|
||||||
// Create another GPS object to hold the state of the boat, with no
|
// Create another GPS object to hold the state of the boat, with no
|
||||||
// communications, so don't call Boat.begin() in setup. We will build some fake
|
// communications, so you don't need to call Boat.begin() in setup.
|
||||||
// sentences from the Boat data to feed to GPS for testing.
|
// We will build some fake sentences from the Boat data to feed to
|
||||||
Adafruit_GPS Boat(&GPSSerial);
|
// GPS for testing.
|
||||||
|
Adafruit_GPS Boat;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Set GPSECHO to 'false' to turn off echoing the GPS data to the Serial console
|
// Set GPSECHO to 'false' to turn off echoing the GPS data to the Serial console
|
||||||
|
|
|
||||||
|
|
@ -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);
|
static bool strStartsWith(const char *str, const char *prefix);
|
||||||
|
|
||||||
/**************************************************************************/
|
|
||||||
/*!
|
|
||||||
@brief Check an NMEA string for basic format, valid source ID and valid
|
|
||||||
and valid sentence ID. Update the values of thisCheck, thisSource and
|
|
||||||
thisSentence.
|
|
||||||
@param nmea Pointer to the NMEA string
|
|
||||||
@return True if well formed, false if it has problems
|
|
||||||
*/
|
|
||||||
/**************************************************************************/
|
|
||||||
bool Adafruit_GPS::check(char *nmea) {
|
|
||||||
thisCheck = 0; // new check
|
|
||||||
if (*nmea != '$')
|
|
||||||
return false; // doesn't start with $
|
|
||||||
else
|
|
||||||
thisCheck += NMEA_HAS_DOLLAR;
|
|
||||||
// do checksum check -- first look if we even have one -- ignore all but last
|
|
||||||
// *
|
|
||||||
char *ast = nmea; // not strchr(nmea,'*'); for first *
|
|
||||||
while (*ast)
|
|
||||||
ast++; // go to the end
|
|
||||||
while (*ast != '*' && ast > nmea)
|
|
||||||
ast--; // then back to * if it's there
|
|
||||||
if (*ast != '*')
|
|
||||||
return false; // there is no asterisk
|
|
||||||
else {
|
|
||||||
uint16_t sum = parseHex(*(ast + 1)) * 16; // extract checksum
|
|
||||||
sum += parseHex(*(ast + 2));
|
|
||||||
char *p = nmea; // check checksum
|
|
||||||
for (char *p1 = p + 1; p1 < ast; p1++)
|
|
||||||
sum ^= *p1;
|
|
||||||
if (sum != 0)
|
|
||||||
return false; // bad checksum :(
|
|
||||||
else
|
|
||||||
thisCheck += NMEA_HAS_CHECKSUM;
|
|
||||||
}
|
|
||||||
// extract source of variable length
|
|
||||||
char *p = nmea + 1;
|
|
||||||
const char *src = tokenOnList(p, sources);
|
|
||||||
if (src) {
|
|
||||||
strcpy(thisSource, src);
|
|
||||||
thisCheck += NMEA_HAS_SOURCE;
|
|
||||||
} else
|
|
||||||
return false;
|
|
||||||
p += strlen(src);
|
|
||||||
// extract sentence id and check if parsed
|
|
||||||
const char *snc = tokenOnList(p, sentences_parsed);
|
|
||||||
if (snc) {
|
|
||||||
strcpy(thisSentence, snc);
|
|
||||||
thisCheck += NMEA_HAS_SENTENCE_P + NMEA_HAS_SENTENCE;
|
|
||||||
} else { // check if known
|
|
||||||
snc = tokenOnList(p, sentences_known);
|
|
||||||
if (snc) {
|
|
||||||
strcpy(thisSentence, snc);
|
|
||||||
thisCheck += NMEA_HAS_SENTENCE;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true; // passed all the tests
|
|
||||||
}
|
|
||||||
|
|
||||||
/**************************************************************************/
|
|
||||||
/*!
|
|
||||||
@brief Check if a token at the start of a string is on a list.
|
|
||||||
@param token Pointer to the string
|
|
||||||
@param list A list of strings, with the final entry starting "ZZ"
|
|
||||||
@return Pointer to the found token, or NULL if it fails
|
|
||||||
*/
|
|
||||||
/**************************************************************************/
|
|
||||||
const char *Adafruit_GPS::tokenOnList(char *token, const char **list) {
|
|
||||||
int i = 0; // index in the list
|
|
||||||
while (strncmp(list[i], "ZZ", 2) &&
|
|
||||||
i < 1000) { // stop at terminator and don't crash without it
|
|
||||||
// test for a match on the sentence name
|
|
||||||
if (!strncmp((const char *)list[i], (const char *)token, strlen(list[i])))
|
|
||||||
return list[i];
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
return NULL; // couldn't find a match
|
|
||||||
}
|
|
||||||
|
|
||||||
/**************************************************************************/
|
|
||||||
/*!
|
|
||||||
@brief Parse a string token from pointer p to the next comma, asterisk
|
|
||||||
or end of string.
|
|
||||||
@param buff Pointer to the buffer to store the string in
|
|
||||||
@param p Pointer into a string
|
|
||||||
@param n Max permitted size of string including terminating 0
|
|
||||||
@return Pointer to the string buffer
|
|
||||||
*/
|
|
||||||
/**************************************************************************/
|
|
||||||
char *Adafruit_GPS::parseStr(char *buff, char *p, int n) {
|
|
||||||
char *e = strchr(p, ',');
|
|
||||||
int len = 0;
|
|
||||||
if (e) {
|
|
||||||
len = min(e - p, n - 1);
|
|
||||||
strncpy(buff, p, len); // copy up to the comma
|
|
||||||
buff[len] = 0;
|
|
||||||
} else {
|
|
||||||
e = strchr(p, '*');
|
|
||||||
if (e) {
|
|
||||||
len = min(e - p, n - 1);
|
|
||||||
strncpy(buff, p, len); // or up to the *
|
|
||||||
buff[e - p] = 0;
|
|
||||||
} else {
|
|
||||||
len = min((int)strlen(p), n - 1);
|
|
||||||
strncpy(buff, p, len); // or to the end or max capacity
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return buff;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**************************************************************************/
|
/**************************************************************************/
|
||||||
/*!
|
/*!
|
||||||
@brief Is the field empty, or should we try conversion? Won't work
|
@brief Is the field empty, or should we try conversion? Won't work
|
||||||
|
|
@ -151,77 +40,30 @@ char *Adafruit_GPS::parseStr(char *buff, char *p, int n) {
|
||||||
@param pStart Pointer to the location of the token in the NMEA string
|
@param pStart Pointer to the location of the token in the NMEA string
|
||||||
@return true if empty field, false if something there
|
@return true if empty field, false if something there
|
||||||
*/
|
*/
|
||||||
/**************************************************************************/
|
|
||||||
bool Adafruit_GPS::isEmpty(char *pStart) {
|
|
||||||
if (',' != *pStart && '*' != *pStart && pStart != NULL)
|
|
||||||
return false;
|
|
||||||
else
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**************************************************************************/
|
|
||||||
/*!
|
|
||||||
@brief Add *CS where CS is the two character hex checksum for all but
|
|
||||||
the first character in the string. The checksum is the result of an
|
|
||||||
exclusive or of all the characters in the string. Also useful if you
|
|
||||||
are creating new PMTK strings for controlling a GPS module and need a
|
|
||||||
checksum added.
|
|
||||||
@param buff Pointer to the string, which must be long enough
|
|
||||||
@return none
|
|
||||||
*/
|
|
||||||
/**************************************************************************/
|
|
||||||
void Adafruit_GPS::addChecksum(char *buff) {
|
|
||||||
char cs = 0;
|
|
||||||
int i = 1;
|
|
||||||
while (buff[i]) {
|
|
||||||
cs ^= buff[i];
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
sprintf(buff, "%s*%02X", buff, cs);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**************************************************************************/
|
|
||||||
/*!
|
|
||||||
@brief Parse a part of an NMEA string for time
|
|
||||||
@param p Pointer to the location of the token in the NMEA string
|
|
||||||
*/
|
|
||||||
/**************************************************************************/
|
|
||||||
void Adafruit_GPS::parseTime(char *p) {
|
|
||||||
// get time
|
|
||||||
uint32_t time = atol(p);
|
|
||||||
hour = time / 10000;
|
|
||||||
minute = (time % 10000) / 100;
|
|
||||||
seconds = (time % 100);
|
|
||||||
|
|
||||||
p = strchr(p, '.') + 1;
|
|
||||||
milliseconds = atoi(p);
|
|
||||||
lastTime = sentTime;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**************************************************************************/
|
/**************************************************************************/
|
||||||
/*!
|
/*!
|
||||||
@brief Parse a part of an NMEA string for latitude angle
|
@brief Parse a part of an NMEA string for latitude angle
|
||||||
@param p Pointer to the location of the token in the NMEA string
|
@param p Pointer to the location of the token in the NMEA string
|
||||||
*/
|
*/
|
||||||
/**************************************************************************/
|
/**************************************************************************/
|
||||||
void Adafruit_GPS::parseLat(char *p) {
|
// void Adafruit_GPS::parseLat(char *p) {
|
||||||
char degreebuff[10];
|
// char degreebuff[10];
|
||||||
if (!isEmpty(p)) {
|
// if (!isEmpty(p)) {
|
||||||
strncpy(degreebuff, p, 2);
|
// strncpy(degreebuff, p, 2);
|
||||||
p += 2;
|
// p += 2;
|
||||||
degreebuff[2] = '\0';
|
// degreebuff[2] = '\0';
|
||||||
long degree = atol(degreebuff) * 10000000;
|
// long degree = atol(degreebuff) * 10000000;
|
||||||
strncpy(degreebuff, p, 2); // minutes
|
// strncpy(degreebuff, p, 2); // minutes
|
||||||
p += 3; // skip decimal point
|
// p += 3; // skip decimal point
|
||||||
strncpy(degreebuff + 2, p, 4);
|
// strncpy(degreebuff + 2, p, 4);
|
||||||
degreebuff[6] = '\0';
|
// degreebuff[6] = '\0';
|
||||||
long minutes = 50 * atol(degreebuff) / 3;
|
// long minutes = 50 * atol(degreebuff) / 3;
|
||||||
latitude_fixed = degree + minutes;
|
// latitude_fixed = degree + minutes;
|
||||||
latitude = degree / 100000 + minutes * 0.000006F;
|
// latitude = degree / 100000 + minutes * 0.000006F;
|
||||||
latitudeDegrees = (latitude - 100 * int(latitude / 100)) / 60.0f;
|
// latitudeDegrees = (latitude - 100 * int(latitude / 100)) / 60.0f;
|
||||||
latitudeDegrees += int(latitude / 100);
|
// latitudeDegrees += int(latitude / 100);
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
/**************************************************************************/
|
/**************************************************************************/
|
||||||
/*!
|
/*!
|
||||||
|
|
@ -230,20 +72,20 @@ void Adafruit_GPS::parseLat(char *p) {
|
||||||
@return True if we parsed it, false if it has invalid data
|
@return True if we parsed it, false if it has invalid data
|
||||||
*/
|
*/
|
||||||
/**************************************************************************/
|
/**************************************************************************/
|
||||||
bool Adafruit_GPS::parseLatDir(char *p) {
|
// bool Adafruit_GPS::parseLatDir(char *p) {
|
||||||
if (p[0] == 'S') {
|
// if (p[0] == 'S') {
|
||||||
lat = 'S';
|
// lat = 'S';
|
||||||
latitudeDegrees *= -1.0f;
|
// latitudeDegrees *= -1.0f;
|
||||||
latitude_fixed *= -1;
|
// latitude_fixed *= -1;
|
||||||
} else if (p[0] == 'N') {
|
// } else if (p[0] == 'N') {
|
||||||
lat = 'N';
|
// lat = 'N';
|
||||||
} else if (p[0] == ',') {
|
// } else if (p[0] == ',') {
|
||||||
lat = 0;
|
// lat = 0;
|
||||||
} else {
|
// } else {
|
||||||
return false;
|
// return false;
|
||||||
}
|
// }
|
||||||
return true;
|
// return true;
|
||||||
}
|
// }
|
||||||
|
|
||||||
/**************************************************************************/
|
/**************************************************************************/
|
||||||
/*!
|
/*!
|
||||||
|
|
@ -251,26 +93,26 @@ bool Adafruit_GPS::parseLatDir(char *p) {
|
||||||
@param p Pointer to the location of the token in the NMEA string
|
@param p Pointer to the location of the token in the NMEA string
|
||||||
*/
|
*/
|
||||||
/**************************************************************************/
|
/**************************************************************************/
|
||||||
void Adafruit_GPS::parseLon(char *p) {
|
// void Adafruit_GPS::parseLon(char *p) {
|
||||||
int32_t degree;
|
// int32_t degree;
|
||||||
long minutes;
|
// long minutes;
|
||||||
char degreebuff[10];
|
// char degreebuff[10];
|
||||||
if (!isEmpty(p)) {
|
// if (!isEmpty(p)) {
|
||||||
strncpy(degreebuff, p, 3);
|
// strncpy(degreebuff, p, 3);
|
||||||
p += 3;
|
// p += 3;
|
||||||
degreebuff[3] = '\0';
|
// degreebuff[3] = '\0';
|
||||||
degree = atol(degreebuff) * 10000000;
|
// degree = atol(degreebuff) * 10000000;
|
||||||
strncpy(degreebuff, p, 2); // minutes
|
// strncpy(degreebuff, p, 2); // minutes
|
||||||
p += 3; // skip decimal point
|
// p += 3; // skip decimal point
|
||||||
strncpy(degreebuff + 2, p, 4);
|
// strncpy(degreebuff + 2, p, 4);
|
||||||
degreebuff[6] = '\0';
|
// degreebuff[6] = '\0';
|
||||||
minutes = 50 * atol(degreebuff) / 3;
|
// minutes = 50 * atol(degreebuff) / 3;
|
||||||
longitude_fixed = degree + minutes;
|
// longitude_fixed = degree + minutes;
|
||||||
longitude = degree / 100000 + minutes * 0.000006F;
|
// longitude = degree / 100000 + minutes * 0.000006F;
|
||||||
longitudeDegrees = (longitude - 100 * int(longitude / 100)) / 60.0f;
|
// longitudeDegrees = (longitude - 100 * int(longitude / 100)) / 60.0f;
|
||||||
longitudeDegrees += int(longitude / 100);
|
// longitudeDegrees += int(longitude / 100);
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
/**************************************************************************/
|
/**************************************************************************/
|
||||||
/*!
|
/*!
|
||||||
|
|
@ -279,81 +121,163 @@ void Adafruit_GPS::parseLon(char *p) {
|
||||||
@return True if we parsed it, false if it has invalid data
|
@return True if we parsed it, false if it has invalid data
|
||||||
*/
|
*/
|
||||||
/**************************************************************************/
|
/**************************************************************************/
|
||||||
bool Adafruit_GPS::parseLonDir(char *p) {
|
// bool Adafruit_GPS::parseLonDir(char *p) {
|
||||||
if (!isEmpty(p)) {
|
// if (!isEmpty(p)) {
|
||||||
if (p[0] == 'W') {
|
// if (p[0] == 'W') {
|
||||||
lon = 'W';
|
// lon = 'W';
|
||||||
longitudeDegrees *= -1.0f;
|
// longitudeDegrees *= -1.0f;
|
||||||
longitude_fixed *= -1;
|
// longitude_fixed *= -1;
|
||||||
} else if (p[0] == 'E') {
|
// } else if (p[0] == 'E') {
|
||||||
lon = 'E';
|
// lon = 'E';
|
||||||
} else if (p[0] == ',') {
|
// } else if (p[0] == ',') {
|
||||||
lon = 0;
|
// lon = 0;
|
||||||
|
// } else {
|
||||||
|
// return false;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// return true;
|
||||||
|
// }
|
||||||
|
|
||||||
|
/**************************************************************************/
|
||||||
|
/*!
|
||||||
|
@brief Start the HW or SW serial port
|
||||||
|
@param baud_or_i2caddr Baud rate if using serial, I2C address if using I2C
|
||||||
|
@returns True on successful hardware init, False on failure
|
||||||
|
*/
|
||||||
|
/**************************************************************************/
|
||||||
|
bool Adafruit_GPS::begin(uint32_t baud_or_i2caddr) {
|
||||||
|
#if (defined(__AVR__) || defined(ESP8266)) && defined(USE_SW_SERIAL)
|
||||||
|
if (gpsSwSerial) {
|
||||||
|
gpsSwSerial->begin(baud_or_i2caddr);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
if (gpsHwSerial) {
|
||||||
|
gpsHwSerial->begin(baud_or_i2caddr);
|
||||||
|
}
|
||||||
|
if (gpsI2C) {
|
||||||
|
gpsI2C->begin();
|
||||||
|
if (baud_or_i2caddr > 0x7F) {
|
||||||
|
_i2caddr = GPS_DEFAULT_I2C_ADDR;
|
||||||
} else {
|
} else {
|
||||||
return false;
|
_i2caddr = baud_or_i2caddr;
|
||||||
|
}
|
||||||
|
// A basic scanner, see if it ACK's
|
||||||
|
gpsI2C->beginTransmission(_i2caddr);
|
||||||
|
return (gpsI2C->endTransmission() == 0);
|
||||||
|
}
|
||||||
|
if (gpsSPI) {
|
||||||
|
gpsSPI->begin();
|
||||||
|
gpsSPI_settings = SPISettings(baud_or_i2caddr, MSBFIRST, SPI_MODE0);
|
||||||
|
if (gpsSPI_cs >= 0) {
|
||||||
|
pinMode(gpsSPI_cs, OUTPUT);
|
||||||
|
digitalWrite(gpsSPI_cs, HIGH);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
delay(10);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**************************************************************************/
|
/**************************************************************************/
|
||||||
/*!
|
/*!
|
||||||
@brief Parse a part of an NMEA string for whether there is a fix
|
@brief Constructor when using SoftwareSerial
|
||||||
@param p Pointer to the location of the token in the NMEA string
|
@param ser Pointer to SoftwareSerial device
|
||||||
@return True if we parsed it, false if it has invalid data
|
|
||||||
*/
|
*/
|
||||||
/**************************************************************************/
|
/**************************************************************************/
|
||||||
bool Adafruit_GPS::parseFix(char *p) {
|
#if (defined(__AVR__) || defined(ESP8266)) && defined(USE_SW_SERIAL)
|
||||||
if (p[0] == 'A') {
|
Adafruit_GPS::Adafruit_GPS(SoftwareSerial *ser) {
|
||||||
fix = true;
|
common_init(); // Set everything to common state, then...
|
||||||
lastFix = sentTime;
|
gpsSwSerial = ser; // ...override gpsSwSerial with value passed.
|
||||||
} else if (p[0] == 'V')
|
}
|
||||||
fix = false;
|
#endif
|
||||||
else
|
|
||||||
return false;
|
/**************************************************************************/
|
||||||
return true;
|
/*!
|
||||||
|
@brief Constructor when using HardwareSerial
|
||||||
|
@param ser Pointer to a HardwareSerial object
|
||||||
|
*/
|
||||||
|
/**************************************************************************/
|
||||||
|
Adafruit_GPS::Adafruit_GPS(HardwareSerial *ser) {
|
||||||
|
common_init(); // Set everything to common state, then...
|
||||||
|
gpsHwSerial = ser; // ...override gpsHwSerial with value passed.
|
||||||
}
|
}
|
||||||
|
|
||||||
/**************************************************************************/
|
/**************************************************************************/
|
||||||
/*!
|
/*!
|
||||||
@brief Time in seconds since the last position fix was obtained. Will
|
@brief Constructor when using I2C
|
||||||
fail by rolling over to zero after one millis() cycle, about 6-1/2 weeks.
|
@param theWire Pointer to an I2C TwoWire object
|
||||||
@return nmea_float_t value in seconds since last fix.
|
|
||||||
*/
|
*/
|
||||||
/**************************************************************************/
|
/**************************************************************************/
|
||||||
nmea_float_t Adafruit_GPS::secondsSinceFix() {
|
Adafruit_GPS::Adafruit_GPS(TwoWire *theWire) {
|
||||||
return (millis() - lastFix) / 1000.;
|
common_init(); // Set everything to common state, then...
|
||||||
|
gpsI2C = theWire; // ...override gpsI2C
|
||||||
}
|
}
|
||||||
|
|
||||||
/**************************************************************************/
|
/**************************************************************************/
|
||||||
/*!
|
/*!
|
||||||
@brief Time in seconds since the last GPS time was obtained. Will fail
|
@brief Constructor when using SPI
|
||||||
by rolling over to zero after one millis() cycle, about 6-1/2 weeks.
|
@param theSPI Pointer to an SPI device object
|
||||||
@return nmea_float_t value in seconds since last GPS time.
|
@param cspin The pin connected to the GPS CS, can be -1 if unused
|
||||||
*/
|
*/
|
||||||
/**************************************************************************/
|
/**************************************************************************/
|
||||||
nmea_float_t Adafruit_GPS::secondsSinceTime() {
|
Adafruit_GPS::Adafruit_GPS(SPIClass *theSPI, int8_t cspin) {
|
||||||
return (millis() - lastTime) / 1000.;
|
common_init(); // Set everything to common state, then...
|
||||||
|
gpsSPI = theSPI; // ...override gpsSPI
|
||||||
|
gpsSPI_cs = cspin;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**************************************************************************/
|
/**************************************************************************/
|
||||||
/*!
|
/*!
|
||||||
@brief Time in seconds since the last GPS date was obtained. Will fail
|
@brief Constructor when there are no communications attached
|
||||||
by rolling over to zero after one millis() cycle, about 6-1/2 weeks.
|
|
||||||
@return nmea_float_t value in seconds since last GPS date.
|
|
||||||
*/
|
*/
|
||||||
/**************************************************************************/
|
/**************************************************************************/
|
||||||
nmea_float_t Adafruit_GPS::secondsSinceDate() {
|
Adafruit_GPS::Adafruit_GPS() {
|
||||||
return (millis() - lastDate) / 1000.;
|
common_init(); // Set everything to common state, then...
|
||||||
|
noComms = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**************************************************************************/
|
/**************************************************************************/
|
||||||
/*!
|
/*!
|
||||||
@brief Fakes time of receipt of a sentence. Use between build() and parse()
|
@brief Initialization code used by all constructor types
|
||||||
to make the timing look like the sentence arrived from the GPS.
|
|
||||||
*/
|
*/
|
||||||
/**************************************************************************/
|
/**************************************************************************/
|
||||||
void Adafruit_GPS::resetSentTime() { sentTime = millis(); }
|
void Adafruit_GPS::common_init(void) {
|
||||||
|
#if (defined(__AVR__) || defined(ESP8266)) && defined(USE_SW_SERIAL)
|
||||||
|
gpsSwSerial = NULL; // Set both to NULL, then override correct
|
||||||
|
#endif
|
||||||
|
gpsHwSerial = NULL; // port pointer in corresponding constructor
|
||||||
|
gpsI2C = NULL;
|
||||||
|
gpsSPI = NULL;
|
||||||
|
recvdflag = false;
|
||||||
|
paused = false;
|
||||||
|
lineidx = 0;
|
||||||
|
currentline = line1;
|
||||||
|
lastline = line2;
|
||||||
|
|
||||||
|
hour = minute = seconds = year = month = day = fixquality = fixquality_3d =
|
||||||
|
satellites = 0; // uint8_t
|
||||||
|
lat = lon = mag = 0; // char
|
||||||
|
fix = false; // bool
|
||||||
|
milliseconds = 0; // uint16_t
|
||||||
|
latitude = longitude = geoidheight = altitude = speed = angle = magvariation =
|
||||||
|
HDOP = VDOP = PDOP = 0.0; // nmea_float_t
|
||||||
|
#ifdef NMEA_EXTENSIONS
|
||||||
|
data_init();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
/**************************************************************************/
|
||||||
|
/*!
|
||||||
|
@brief Destroy the object.
|
||||||
|
@return none
|
||||||
|
*/
|
||||||
|
/**************************************************************************/
|
||||||
|
Adafruit_GPS::~Adafruit_GPS() {
|
||||||
|
#ifdef NMEA_EXTENSIONS
|
||||||
|
for (int i = 0; i < (int)NMEA_MAX_INDEX; i++)
|
||||||
|
removeHistory((nmea_index_t)i); // to free any history mallocs
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
/**************************************************************************/
|
/**************************************************************************/
|
||||||
/*!
|
/*!
|
||||||
|
|
@ -434,7 +358,7 @@ char Adafruit_GPS::read(void) {
|
||||||
uint32_t tStart = millis(); // as close as we can get to time char was sent
|
uint32_t tStart = millis(); // as close as we can get to time char was sent
|
||||||
char c = 0;
|
char c = 0;
|
||||||
|
|
||||||
if (paused)
|
if (paused || noComms)
|
||||||
return c;
|
return c;
|
||||||
|
|
||||||
#if (defined(__AVR__) || defined(ESP8266)) && defined(USE_SW_SERIAL)
|
#if (defined(__AVR__) || defined(ESP8266)) && defined(USE_SW_SERIAL)
|
||||||
|
|
@ -530,121 +454,6 @@ char Adafruit_GPS::read(void) {
|
||||||
return c;
|
return c;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**************************************************************************/
|
|
||||||
/*!
|
|
||||||
@brief Constructor when using SoftwareSerial
|
|
||||||
@param ser Pointer to SoftwareSerial device
|
|
||||||
*/
|
|
||||||
/**************************************************************************/
|
|
||||||
#if (defined(__AVR__) || defined(ESP8266)) && defined(USE_SW_SERIAL)
|
|
||||||
Adafruit_GPS::Adafruit_GPS(SoftwareSerial *ser) {
|
|
||||||
common_init(); // Set everything to common state, then...
|
|
||||||
gpsSwSerial = ser; // ...override gpsSwSerial with value passed.
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/**************************************************************************/
|
|
||||||
/*!
|
|
||||||
@brief Constructor when using HardwareSerial
|
|
||||||
@param ser Pointer to a HardwareSerial object
|
|
||||||
*/
|
|
||||||
/**************************************************************************/
|
|
||||||
Adafruit_GPS::Adafruit_GPS(HardwareSerial *ser) {
|
|
||||||
common_init(); // Set everything to common state, then...
|
|
||||||
gpsHwSerial = ser; // ...override gpsHwSerial with value passed.
|
|
||||||
}
|
|
||||||
|
|
||||||
/**************************************************************************/
|
|
||||||
/*!
|
|
||||||
@brief Constructor when using I2C
|
|
||||||
@param theWire Pointer to an I2C TwoWire object
|
|
||||||
*/
|
|
||||||
/**************************************************************************/
|
|
||||||
Adafruit_GPS::Adafruit_GPS(TwoWire *theWire) {
|
|
||||||
common_init(); // Set everything to common state, then...
|
|
||||||
gpsI2C = theWire; // ...override gpsI2C
|
|
||||||
}
|
|
||||||
|
|
||||||
/**************************************************************************/
|
|
||||||
/*!
|
|
||||||
@brief Constructor when using SPI
|
|
||||||
@param theSPI Pointer to an SPI device object
|
|
||||||
@param cspin The pin connected to the GPS CS, can be -1 if unused
|
|
||||||
*/
|
|
||||||
/**************************************************************************/
|
|
||||||
Adafruit_GPS::Adafruit_GPS(SPIClass *theSPI, int8_t cspin) {
|
|
||||||
common_init(); // Set everything to common state, then...
|
|
||||||
gpsSPI = theSPI; // ...override gpsSPI
|
|
||||||
gpsSPI_cs = cspin;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**************************************************************************/
|
|
||||||
/*!
|
|
||||||
@brief Initialization code used by all constructor types
|
|
||||||
*/
|
|
||||||
/**************************************************************************/
|
|
||||||
void Adafruit_GPS::common_init(void) {
|
|
||||||
#if (defined(__AVR__) || defined(ESP8266)) && defined(USE_SW_SERIAL)
|
|
||||||
gpsSwSerial = NULL; // Set both to NULL, then override correct
|
|
||||||
#endif
|
|
||||||
gpsHwSerial = NULL; // port pointer in corresponding constructor
|
|
||||||
gpsI2C = NULL;
|
|
||||||
gpsSPI = NULL;
|
|
||||||
recvdflag = false;
|
|
||||||
paused = false;
|
|
||||||
lineidx = 0;
|
|
||||||
currentline = line1;
|
|
||||||
lastline = line2;
|
|
||||||
|
|
||||||
hour = minute = seconds = year = month = day = fixquality = fixquality_3d =
|
|
||||||
satellites = 0; // uint8_t
|
|
||||||
lat = lon = mag = 0; // char
|
|
||||||
fix = false; // bool
|
|
||||||
milliseconds = 0; // uint16_t
|
|
||||||
latitude = longitude = geoidheight = altitude = speed = angle = magvariation =
|
|
||||||
HDOP = VDOP = PDOP = 0.0; // nmea_float_t
|
|
||||||
}
|
|
||||||
|
|
||||||
/**************************************************************************/
|
|
||||||
/*!
|
|
||||||
@brief Start the HW or SW serial port
|
|
||||||
@param baud_or_i2caddr Baud rate if using serial, I2C address if using I2C
|
|
||||||
@returns True on successful hardware init, False on failure
|
|
||||||
*/
|
|
||||||
/**************************************************************************/
|
|
||||||
bool Adafruit_GPS::begin(uint32_t baud_or_i2caddr) {
|
|
||||||
#if (defined(__AVR__) || defined(ESP8266)) && defined(USE_SW_SERIAL)
|
|
||||||
if (gpsSwSerial) {
|
|
||||||
gpsSwSerial->begin(baud_or_i2caddr);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
if (gpsHwSerial) {
|
|
||||||
gpsHwSerial->begin(baud_or_i2caddr);
|
|
||||||
}
|
|
||||||
if (gpsI2C) {
|
|
||||||
gpsI2C->begin();
|
|
||||||
if (baud_or_i2caddr > 0x7F) {
|
|
||||||
_i2caddr = GPS_DEFAULT_I2C_ADDR;
|
|
||||||
} else {
|
|
||||||
_i2caddr = baud_or_i2caddr;
|
|
||||||
}
|
|
||||||
// A basic scanner, see if it ACK's
|
|
||||||
gpsI2C->beginTransmission(_i2caddr);
|
|
||||||
return (gpsI2C->endTransmission() == 0);
|
|
||||||
}
|
|
||||||
if (gpsSPI) {
|
|
||||||
gpsSPI->begin();
|
|
||||||
gpsSPI_settings = SPISettings(baud_or_i2caddr, MSBFIRST, SPI_MODE0);
|
|
||||||
if (gpsSPI_cs >= 0) {
|
|
||||||
pinMode(gpsSPI_cs, OUTPUT);
|
|
||||||
digitalWrite(gpsSPI_cs, HIGH);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
delay(10);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**************************************************************************/
|
/**************************************************************************/
|
||||||
/*!
|
/*!
|
||||||
@brief Send a command to the GPS device
|
@brief Send a command to the GPS device
|
||||||
|
|
@ -680,28 +489,6 @@ char *Adafruit_GPS::lastNMEA(void) {
|
||||||
return (char *)lastline;
|
return (char *)lastline;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**************************************************************************/
|
|
||||||
/*!
|
|
||||||
@brief Parse a hex character and return the appropriate decimal value
|
|
||||||
@param c Hex character, e.g. '0' or 'B'
|
|
||||||
@return Integer value of the hex character. Returns 0 if c is not a proper
|
|
||||||
character
|
|
||||||
*/
|
|
||||||
/**************************************************************************/
|
|
||||||
// read a Hex value and return the decimal equivalent
|
|
||||||
uint8_t Adafruit_GPS::parseHex(char c) {
|
|
||||||
if (c < '0')
|
|
||||||
return 0;
|
|
||||||
if (c <= '9')
|
|
||||||
return c - '0';
|
|
||||||
if (c < 'A')
|
|
||||||
return 0;
|
|
||||||
if (c <= 'F')
|
|
||||||
return (c - 'A') + 10;
|
|
||||||
// if (c > 'F')
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**************************************************************************/
|
/**************************************************************************/
|
||||||
/*!
|
/*!
|
||||||
@brief Wait for a specified sentence from the device
|
@brief Wait for a specified sentence from the device
|
||||||
|
|
@ -842,6 +629,47 @@ bool Adafruit_GPS::wakeup(void) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**************************************************************************/
|
||||||
|
/*!
|
||||||
|
@brief Time in seconds since the last position fix was obtained. Will
|
||||||
|
fail by rolling over to zero after one millis() cycle, about 6-1/2 weeks.
|
||||||
|
@return nmea_float_t value in seconds since last fix.
|
||||||
|
*/
|
||||||
|
/**************************************************************************/
|
||||||
|
nmea_float_t Adafruit_GPS::secondsSinceFix() {
|
||||||
|
return (millis() - lastFix) / 1000.;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**************************************************************************/
|
||||||
|
/*!
|
||||||
|
@brief Time in seconds since the last GPS time was obtained. Will fail
|
||||||
|
by rolling over to zero after one millis() cycle, about 6-1/2 weeks.
|
||||||
|
@return nmea_float_t value in seconds since last GPS time.
|
||||||
|
*/
|
||||||
|
/**************************************************************************/
|
||||||
|
nmea_float_t Adafruit_GPS::secondsSinceTime() {
|
||||||
|
return (millis() - lastTime) / 1000.;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**************************************************************************/
|
||||||
|
/*!
|
||||||
|
@brief Time in seconds since the last GPS date was obtained. Will fail
|
||||||
|
by rolling over to zero after one millis() cycle, about 6-1/2 weeks.
|
||||||
|
@return nmea_float_t value in seconds since last GPS date.
|
||||||
|
*/
|
||||||
|
/**************************************************************************/
|
||||||
|
nmea_float_t Adafruit_GPS::secondsSinceDate() {
|
||||||
|
return (millis() - lastDate) / 1000.;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**************************************************************************/
|
||||||
|
/*!
|
||||||
|
@brief Fakes time of receipt of a sentence. Use between build() and parse()
|
||||||
|
to make the timing look like the sentence arrived from the GPS.
|
||||||
|
*/
|
||||||
|
/**************************************************************************/
|
||||||
|
void Adafruit_GPS::resetSentTime() { sentTime = millis(); }
|
||||||
|
|
||||||
/**************************************************************************/
|
/**************************************************************************/
|
||||||
/*!
|
/*!
|
||||||
@brief Checks whether a string starts with a specified prefix
|
@brief Checks whether a string starts with a specified prefix
|
||||||
|
|
|
||||||
|
|
@ -21,8 +21,6 @@
|
||||||
*/
|
*/
|
||||||
/**************************************************************************/
|
/**************************************************************************/
|
||||||
|
|
||||||
// Fllybob added lines 34,35 and 40,41 to add 100mHz logging capability
|
|
||||||
|
|
||||||
#ifndef _ADAFRUIT_GPS_H
|
#ifndef _ADAFRUIT_GPS_H
|
||||||
#define _ADAFRUIT_GPS_H
|
#define _ADAFRUIT_GPS_H
|
||||||
|
|
||||||
|
|
@ -31,9 +29,15 @@
|
||||||
Comment out the definition of NMEA_EXTENSIONS to make the library use as
|
Comment out the definition of NMEA_EXTENSIONS to make the library use as
|
||||||
little memory as possible for GPS functionality only. The ARDUINO_ARCH_AVR
|
little memory as possible for GPS functionality only. The ARDUINO_ARCH_AVR
|
||||||
test should leave it out of any compilations for the UNO and similar. */
|
test should leave it out of any compilations for the UNO and similar. */
|
||||||
|
#ifndef NMEA_EXTRAS // inject on the compile command line to force extensions
|
||||||
#ifndef ARDUINO_ARCH_AVR
|
#ifndef ARDUINO_ARCH_AVR
|
||||||
#define NMEA_EXTENSIONS ///< if defined will include more NMEA sentences
|
#define NMEA_EXTENSIONS ///< if defined will include more NMEA sentences
|
||||||
#endif
|
#endif
|
||||||
|
#else
|
||||||
|
#if (NMEA_EXTRAS > 0)
|
||||||
|
#define NMEA_EXTENSIONS ///< if defined will include more NMEA sentences
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
#define USE_SW_SERIAL ///< comment this out if you don't want to include
|
#define USE_SW_SERIAL ///< comment this out if you don't want to include
|
||||||
///< software serial in the library
|
///< software serial in the library
|
||||||
|
|
@ -60,8 +64,9 @@
|
||||||
|
|
||||||
/// type for resulting code from running check()
|
/// type for resulting code from running check()
|
||||||
typedef enum {
|
typedef enum {
|
||||||
NMEA_BAD = 0, ///< passed none of the checks
|
NMEA_BAD = 0, ///< passed none of the checks
|
||||||
NMEA_HAS_DOLLAR = 1, ///< has a dollar sign in the first position
|
NMEA_HAS_DOLLAR =
|
||||||
|
1, ///< has a dollar sign or exclamation mark in the first position
|
||||||
NMEA_HAS_CHECKSUM = 2, ///< has a valid checksum at the end
|
NMEA_HAS_CHECKSUM = 2, ///< has a valid checksum at the end
|
||||||
NMEA_HAS_NAME = 4, ///< there is a token after the $ followed by a comma
|
NMEA_HAS_NAME = 4, ///< there is a token after the $ followed by a comma
|
||||||
NMEA_HAS_SOURCE = 10, ///< has a recognized source ID
|
NMEA_HAS_SOURCE = 10, ///< has a recognized source ID
|
||||||
|
|
@ -75,6 +80,7 @@ typedef enum {
|
||||||
*/
|
*/
|
||||||
class Adafruit_GPS : public Print {
|
class Adafruit_GPS : public Print {
|
||||||
public:
|
public:
|
||||||
|
// Adafruit_GPS.cpp
|
||||||
bool begin(uint32_t baud_or_i2caddr);
|
bool begin(uint32_t baud_or_i2caddr);
|
||||||
|
|
||||||
#if (defined(__AVR__) || defined(ESP8266)) && defined(USE_SW_SERIAL)
|
#if (defined(__AVR__) || defined(ESP8266)) && defined(USE_SW_SERIAL)
|
||||||
|
|
@ -83,31 +89,60 @@ public:
|
||||||
Adafruit_GPS(HardwareSerial *ser); // Constructor when using HardwareSerial
|
Adafruit_GPS(HardwareSerial *ser); // Constructor when using HardwareSerial
|
||||||
Adafruit_GPS(TwoWire *theWire); // Constructor when using I2C
|
Adafruit_GPS(TwoWire *theWire); // Constructor when using I2C
|
||||||
Adafruit_GPS(SPIClass *theSPI, int8_t cspin); // Constructor when using SPI
|
Adafruit_GPS(SPIClass *theSPI, int8_t cspin); // Constructor when using SPI
|
||||||
|
Adafruit_GPS(); // Constructor for no communications, just data storage
|
||||||
char *lastNMEA(void);
|
|
||||||
bool newNMEAreceived();
|
|
||||||
void common_init(void);
|
void common_init(void);
|
||||||
|
virtual ~Adafruit_GPS();
|
||||||
|
|
||||||
void sendCommand(const char *);
|
|
||||||
|
|
||||||
void pause(bool b);
|
|
||||||
|
|
||||||
uint8_t parseHex(char c);
|
|
||||||
|
|
||||||
char read(void);
|
|
||||||
size_t write(uint8_t);
|
|
||||||
size_t available(void);
|
size_t available(void);
|
||||||
|
size_t write(uint8_t);
|
||||||
bool check(char *nmea);
|
char read(void);
|
||||||
bool parse(char *);
|
void sendCommand(const char *);
|
||||||
void addChecksum(char *buff);
|
bool newNMEAreceived();
|
||||||
|
void pause(bool b);
|
||||||
|
char *lastNMEA(void);
|
||||||
|
bool waitForSentence(const char *wait, uint8_t max = MAXWAITSENTENCE,
|
||||||
|
bool usingInterrupts = false);
|
||||||
|
bool LOCUS_StartLogger(void);
|
||||||
|
bool LOCUS_StopLogger(void);
|
||||||
|
bool LOCUS_ReadStatus(void);
|
||||||
|
bool standby(void);
|
||||||
|
bool wakeup(void);
|
||||||
nmea_float_t secondsSinceFix();
|
nmea_float_t secondsSinceFix();
|
||||||
nmea_float_t secondsSinceTime();
|
nmea_float_t secondsSinceTime();
|
||||||
nmea_float_t secondsSinceDate();
|
nmea_float_t secondsSinceDate();
|
||||||
void resetSentTime();
|
void resetSentTime();
|
||||||
|
|
||||||
bool wakeup(void);
|
// NMEA_parse.cpp
|
||||||
bool standby(void);
|
bool parse(char *);
|
||||||
|
bool check(char *nmea);
|
||||||
|
bool onList(char *nmea, const char **list);
|
||||||
|
uint8_t parseHex(char c);
|
||||||
|
|
||||||
|
// NMEA_build.cpp
|
||||||
|
#ifdef NMEA_EXTENSIONS
|
||||||
|
char *build(char *nmea, const char *thisSource, const char *thisSentence,
|
||||||
|
char ref = 'R');
|
||||||
|
#endif
|
||||||
|
void addChecksum(char *buff);
|
||||||
|
|
||||||
|
// NMEA_data.cpp
|
||||||
|
void newDataValue(nmea_index_t tag, nmea_float_t v);
|
||||||
|
#ifdef NMEA_EXTENSIONS
|
||||||
|
nmea_float_t get(nmea_index_t idx);
|
||||||
|
nmea_float_t getSmoothed(nmea_index_t idx);
|
||||||
|
void initDataValue(nmea_index_t idx, char *label = NULL, char *fmt = NULL,
|
||||||
|
char *unit = NULL, unsigned long response = 0,
|
||||||
|
nmea_value_type_t type = NMEA_SIMPLE_FLOAT);
|
||||||
|
nmea_history_t *initHistory(nmea_index_t idx, nmea_float_t scale = 10.0,
|
||||||
|
nmea_float_t offset = 0.0,
|
||||||
|
unsigned historyInterval = 20,
|
||||||
|
unsigned historyN = 192);
|
||||||
|
void removeHistory(nmea_index_t idx);
|
||||||
|
void showDataValue(nmea_index_t idx, int n = 7);
|
||||||
|
bool isCompoundAngle(nmea_index_t idx);
|
||||||
|
#endif
|
||||||
|
nmea_float_t boatAngle(nmea_float_t s, nmea_float_t c);
|
||||||
|
nmea_float_t compassAngle(nmea_float_t s, nmea_float_t c);
|
||||||
|
|
||||||
int thisCheck = 0; ///< the results of the check on the current sentence
|
int thisCheck = 0; ///< the results of the check on the current sentence
|
||||||
char thisSource[NMEA_MAX_SOURCE_ID] = {
|
char thisSource[NMEA_MAX_SOURCE_ID] = {
|
||||||
|
|
@ -162,12 +197,6 @@ public:
|
||||||
uint8_t fixquality_3d; ///< 3D fix quality (1, 3, 3 = Nofix, 2D fix, 3D fix)
|
uint8_t fixquality_3d; ///< 3D fix quality (1, 3, 3 = Nofix, 2D fix, 3D fix)
|
||||||
uint8_t satellites; ///< Number of satellites in use
|
uint8_t satellites; ///< Number of satellites in use
|
||||||
|
|
||||||
bool waitForSentence(const char *wait, uint8_t max = MAXWAITSENTENCE,
|
|
||||||
bool usingInterrupts = false);
|
|
||||||
bool LOCUS_StartLogger(void);
|
|
||||||
bool LOCUS_StopLogger(void);
|
|
||||||
bool LOCUS_ReadStatus(void);
|
|
||||||
|
|
||||||
uint16_t LOCUS_serial; ///< Log serial number
|
uint16_t LOCUS_serial; ///< Log serial number
|
||||||
uint16_t LOCUS_records; ///< Log number of data record
|
uint16_t LOCUS_records; ///< Log number of data record
|
||||||
uint8_t LOCUS_type; ///< Log type, 0: Overlap, 1: FullStop
|
uint8_t LOCUS_type; ///< Log type, 0: Overlap, 1: FullStop
|
||||||
|
|
@ -180,11 +209,20 @@ public:
|
||||||
uint8_t LOCUS_percent; ///< Log life used percentage
|
uint8_t LOCUS_percent; ///< Log life used percentage
|
||||||
|
|
||||||
#ifdef NMEA_EXTENSIONS
|
#ifdef NMEA_EXTENSIONS
|
||||||
// NMEA additional public functions
|
|
||||||
char *build(char *nmea, const char *thisSource, const char *thisSentence,
|
|
||||||
char ref = 'R');
|
|
||||||
|
|
||||||
// NMEA additional public variables
|
// NMEA additional public variables
|
||||||
|
nmea_datavalue_t
|
||||||
|
val[NMEA_MAX_INDEX]; ///< an array of data value structs, val[0] = most
|
||||||
|
///< recent HDOP so that ockam indexing works
|
||||||
|
nmea_float_t depthToKeel =
|
||||||
|
2.4; ///< depth from surface to bottom of keel in metres
|
||||||
|
nmea_float_t depthToTransducer =
|
||||||
|
0.0; ///< depth of transducer below the surface in metres
|
||||||
|
|
||||||
|
char toID[NMEA_MAX_WP_ID] = {
|
||||||
|
0}; ///< id of waypoint going to on this segment of the route
|
||||||
|
char fromID[NMEA_MAX_WP_ID] = {
|
||||||
|
0}; ///< id of waypoint coming from on this segment of the route
|
||||||
|
|
||||||
char txtTXT[63] = {0}; ///< text content from most recent TXT sentence
|
char txtTXT[63] = {0}; ///< text content from most recent TXT sentence
|
||||||
int txtTot = 0; ///< total TXT sentences in group
|
int txtTot = 0; ///< total TXT sentences in group
|
||||||
int txtID = 0; ///< id of the text message
|
int txtID = 0; ///< id of the text message
|
||||||
|
|
@ -192,22 +230,41 @@ public:
|
||||||
#endif // NMEA_EXTENSIONS
|
#endif // NMEA_EXTENSIONS
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
// void parseLat(char *);
|
||||||
|
// bool parseLatDir(char *);
|
||||||
|
// void parseLon(char *);
|
||||||
|
// bool parseLonDir(char *);
|
||||||
|
// NMEA_data.cpp
|
||||||
|
void data_init();
|
||||||
|
// NMEA_parse.cpp
|
||||||
const char *tokenOnList(char *token, const char **list);
|
const char *tokenOnList(char *token, const char **list);
|
||||||
|
bool parseCoord(char *p, nmea_float_t *angleDegrees = NULL,
|
||||||
|
nmea_float_t *angle = NULL, int32_t *angle_fixed = NULL,
|
||||||
|
char *dir = NULL);
|
||||||
char *parseStr(char *buff, char *p, int n);
|
char *parseStr(char *buff, char *p, int n);
|
||||||
bool isEmpty(char *pStart);
|
bool parseTime(char *);
|
||||||
void parseTime(char *);
|
|
||||||
void parseLat(char *);
|
|
||||||
bool parseLatDir(char *);
|
|
||||||
void parseLon(char *);
|
|
||||||
bool parseLonDir(char *);
|
|
||||||
bool parseFix(char *);
|
bool parseFix(char *);
|
||||||
|
bool isEmpty(char *pStart);
|
||||||
|
|
||||||
// used by check() for validity tests, room for future expansion
|
// used by check() for validity tests, room for future expansion
|
||||||
const char *sources[5] = {"II", "WI", "GP", "GN",
|
const char *sources[6] = {"II", "WI", "GP",
|
||||||
"ZZZ"}; ///< valid source ids
|
"GN", "P", "ZZZ"}; ///< valid source ids
|
||||||
|
#ifdef NMEA_EXTENSIONS
|
||||||
|
const char
|
||||||
|
*sentences_parsed[20] =
|
||||||
|
{
|
||||||
|
"GGA", "GLL", "GSA", "RMC", "DBT", "HDM", "HDT",
|
||||||
|
"MDA", "MTW", "MWV", "RMB", "TXT", "VHW", "VLW",
|
||||||
|
"VPW", "VWR", "WCV", "XTE", "ZZZ"}; ///< parseable sentence ids
|
||||||
|
const char *sentences_known[15] = {
|
||||||
|
"APB", "DPT", "GSV", "HDG", "MWD", "ROT",
|
||||||
|
"RPM", "RSA", "VDR", "VTG", "ZDA", "ZZZ"}; ///< known, but not parseable
|
||||||
|
#else // make the lists short to save memory
|
||||||
const char *sentences_parsed[5] = {"GGA", "GLL", "GSA", "RMC",
|
const char *sentences_parsed[5] = {"GGA", "GLL", "GSA", "RMC",
|
||||||
"ZZZ"}; ///< parseable sentence ids
|
"ZZZ"}; ///< parseable sentence ids
|
||||||
const char *sentences_known[1] = {
|
const char *sentences_known[4] = {"DBT", "HDM", "HDT",
|
||||||
"ZZZ"}; ///< known, but not parseable sentence ids
|
"ZZZ"}; ///< known, but not parseable
|
||||||
|
#endif
|
||||||
|
|
||||||
// Make all of these times far in the past by setting them near the middle of
|
// Make all of these times far in the past by setting them near the middle of
|
||||||
// the millis() range. Timing assumes that sentences are parsed promptly.
|
// the millis() range. Timing assumes that sentences are parsed promptly.
|
||||||
|
|
@ -226,6 +283,7 @@ private:
|
||||||
#if (defined(__AVR__) || defined(ESP8266)) && defined(USE_SW_SERIAL)
|
#if (defined(__AVR__) || defined(ESP8266)) && defined(USE_SW_SERIAL)
|
||||||
SoftwareSerial *gpsSwSerial;
|
SoftwareSerial *gpsSwSerial;
|
||||||
#endif
|
#endif
|
||||||
|
bool noComms = false;
|
||||||
HardwareSerial *gpsHwSerial;
|
HardwareSerial *gpsHwSerial;
|
||||||
TwoWire *gpsI2C;
|
TwoWire *gpsI2C;
|
||||||
SPIClass *gpsSPI;
|
SPIClass *gpsSPI;
|
||||||
|
|
|
||||||
|
|
@ -45,10 +45,16 @@
|
||||||
|
|
||||||
build() will not work properly in an environment that does not support
|
build() will not work properly in an environment that does not support
|
||||||
the %f floating point formatter in sprintf(), and will return NULL.
|
the %f floating point formatter in sprintf(), and will return NULL.
|
||||||
|
Floating point arguments to sprintf() are explicitly cast to double to
|
||||||
|
avoid warnings in some compilers.
|
||||||
|
|
||||||
build() adds Carriage Return and Line Feed to sentences to conform to
|
build() adds Carriage Return and Line Feed to sentences to conform to
|
||||||
NMEA-183, so send your output with a print, not a println.
|
NMEA-183, so send your output with a print, not a println.
|
||||||
|
|
||||||
|
The resulting sentence may be corrupted if the input data is corrupt.
|
||||||
|
In particular, the sentence will be truncated if any of the character
|
||||||
|
data is 0, e.g. if lat is not set to 'N' or 'S'.
|
||||||
|
|
||||||
Some of the data in these test sentences may be arbitrary, e.g. for the
|
Some of the data in these test sentences may be arbitrary, e.g. for the
|
||||||
TXT sentence which has a more complicated protocol for multiple lines
|
TXT sentence which has a more complicated protocol for multiple lines
|
||||||
sent as a message set. Also, the data in the class variables are presumed
|
sent as a message set. Also, the data in the class variables are presumed
|
||||||
|
|
@ -66,7 +72,8 @@
|
||||||
/**************************************************************************/
|
/**************************************************************************/
|
||||||
char *Adafruit_GPS::build(char *nmea, const char *thisSource,
|
char *Adafruit_GPS::build(char *nmea, const char *thisSource,
|
||||||
const char *thisSentence, char ref) {
|
const char *thisSentence, char ref) {
|
||||||
sprintf(nmea, "%6.2f", 123.45); // fail if sprintf() doesn't handle floats
|
sprintf(nmea, "%6.2f",
|
||||||
|
(double)123.45); // fail if sprintf() doesn't handle floats
|
||||||
if (strcmp(nmea, "123.45"))
|
if (strcmp(nmea, "123.45"))
|
||||||
return NULL;
|
return NULL;
|
||||||
*nmea = '$';
|
*nmea = '$';
|
||||||
|
|
@ -82,8 +89,7 @@ char *Adafruit_GPS::build(char *nmea, const char *thisSource,
|
||||||
// pruning excess code easier. Otherwise, keep them alphabetical for ease of
|
// pruning excess code easier. Otherwise, keep them alphabetical for ease of
|
||||||
// reading.
|
// reading.
|
||||||
|
|
||||||
if (!strcmp(thisSentence,
|
if (!strcmp(thisSentence, "GGA")) { //************************************GGA
|
||||||
"GGA")) { //********************************************GGA
|
|
||||||
// GGA Global Positioning System Fix Data. Time, Position and fix related
|
// GGA Global Positioning System Fix Data. Time, Position and fix related
|
||||||
// data for a GPS receiver
|
// data for a GPS receiver
|
||||||
// 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
|
// 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
|
||||||
|
|
@ -107,12 +113,12 @@ char *Adafruit_GPS::build(char *nmea, const char *thisSource,
|
||||||
// 14) Differential reference station ID, 0000-1023
|
// 14) Differential reference station ID, 0000-1023
|
||||||
// 15) Checksum
|
// 15) Checksum
|
||||||
sprintf(p, "%09.2f,%09.4f,%c,%010.4f,%c,%d,%02d,%f,%f,M,%f,M,,",
|
sprintf(p, "%09.2f,%09.4f,%c,%010.4f,%c,%d,%02d,%f,%f,M,%f,M,,",
|
||||||
hour * 10000L + minute * 100L + seconds + milliseconds / 1000.,
|
(double)hour * 10000L + minute * 100L + seconds +
|
||||||
|
milliseconds / 1000.,
|
||||||
(double)latitude, lat, (double)longitude, lon, fixquality,
|
(double)latitude, lat, (double)longitude, lon, fixquality,
|
||||||
satellites, (double)HDOP, (double)altitude, (double)geoidheight);
|
satellites, (double)HDOP, (double)altitude, (double)geoidheight);
|
||||||
|
|
||||||
} else if (!strcmp(thisSentence,
|
} else if (!strcmp(thisSentence, "GLL")) { //*****************************GLL
|
||||||
"GLL")) { //********************************************GLL
|
|
||||||
// GLL Geographic Position – Latitude/Longitude
|
// GLL Geographic Position – Latitude/Longitude
|
||||||
// 1 2 3 4 5 6 7
|
// 1 2 3 4 5 6 7
|
||||||
// | | | | | | |
|
// | | | | | | |
|
||||||
|
|
@ -126,10 +132,10 @@ char *Adafruit_GPS::build(char *nmea, const char *thisSource,
|
||||||
// 7) Checksum
|
// 7) Checksum
|
||||||
sprintf(p, "%09.4f,%c,%010.4f,%c,%09.2f,A", (double)latitude, lat,
|
sprintf(p, "%09.4f,%c,%010.4f,%c,%09.2f,A", (double)latitude, lat,
|
||||||
(double)longitude, lon,
|
(double)longitude, lon,
|
||||||
hour * 10000L + minute * 100L + seconds + milliseconds / 1000.);
|
(double)hour * 10000L + minute * 100L + seconds +
|
||||||
|
milliseconds / 1000.);
|
||||||
|
|
||||||
} else if (!strcmp(thisSentence,
|
} else if (!strcmp(thisSentence, "GSA")) { //*****************************GSA
|
||||||
"GSA")) { //********************************************
|
|
||||||
// GSA GPS DOP and active satellites
|
// GSA GPS DOP and active satellites
|
||||||
// 1 2 3 14 15 16 17 18
|
// 1 2 3 14 15 16 17 18
|
||||||
// | | | | | | | |
|
// | | | | | | | |
|
||||||
|
|
@ -146,8 +152,7 @@ char *Adafruit_GPS::build(char *nmea, const char *thisSource,
|
||||||
// 18) Checksum
|
// 18) Checksum
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
||||||
} else if (!strcmp(thisSentence,
|
} else if (!strcmp(thisSentence, "RMC")) { //*****************************RMC
|
||||||
"RMC")) { //********************************************RMC
|
|
||||||
// RMC Recommended Minimum Navigation Information
|
// RMC Recommended Minimum Navigation Information
|
||||||
// 12
|
// 12
|
||||||
// 1 2 3 4 5 6 7 8 9 10 11 |
|
// 1 2 3 4 5 6 7 8 9 10 11 |
|
||||||
|
|
@ -166,13 +171,257 @@ char *Adafruit_GPS::build(char *nmea, const char *thisSource,
|
||||||
// 11) E or W
|
// 11) E or W
|
||||||
// 12) Checksum
|
// 12) Checksum
|
||||||
sprintf(p, "%09.2f,A,%09.4f,%c,%010.4f,%c,%f,%f,%06d,%f,%c",
|
sprintf(p, "%09.2f,A,%09.4f,%c,%010.4f,%c,%f,%f,%06d,%f,%c",
|
||||||
hour * 10000L + minute * 100L + seconds + milliseconds / 1000.,
|
(double)hour * 10000L + minute * 100L + seconds +
|
||||||
|
milliseconds / 1000.,
|
||||||
(double)latitude, lat, (double)longitude, lon, (double)speed,
|
(double)latitude, lat, (double)longitude, lon, (double)speed,
|
||||||
(double)angle, day * 10000 + month * 100 + year,
|
(double)angle, day * 10000 + month * 100 + year,
|
||||||
(double)magvariation, mag);
|
(double)magvariation, mag);
|
||||||
|
|
||||||
} else if (!strcmp(thisSentence,
|
} else if (!strcmp(thisSentence, "APB")) { //*****************************APB
|
||||||
"TXT")) { //********************************************TXT
|
// APB Autopilot Sentence "B"
|
||||||
|
// 13 15
|
||||||
|
// 1 2 3 4 5 6 7 8 9 10 11 12 | 14 |
|
||||||
|
// | | | | | | | | | | | | | | |
|
||||||
|
//$--APB,A,A,x.x,a,N,A,A,x.x,a,c--c,x.x,a,x.x,a*hh
|
||||||
|
// 1) Status
|
||||||
|
// V = LORAN-C Blink or SNR warning
|
||||||
|
// A = general warning flag or other navigation systems when a reliable
|
||||||
|
// fix is not available
|
||||||
|
// 2) Status
|
||||||
|
// V = Loran-C Cycle Lock warning flag
|
||||||
|
// A = OK or not used
|
||||||
|
// 3) Cross Track Error Magnitude
|
||||||
|
// 4) Direction to steer, L or R
|
||||||
|
// 5) Cross Track Units, N = Nautical Miles
|
||||||
|
// 6) Status
|
||||||
|
// A = Arrival Circle Entered
|
||||||
|
// 7) Status
|
||||||
|
// A = Perpendicular passed at waypoint
|
||||||
|
// 8) Bearing origin to destination
|
||||||
|
// 9) M = Magnetic, T = True
|
||||||
|
// 10) Destination Waypoint ID
|
||||||
|
// 11) Bearing, present position to Destination
|
||||||
|
// 12) M = Magnetic, T = True
|
||||||
|
// 13) Heading to steer to destination waypoint
|
||||||
|
// 14) M = Magnetic, T = True
|
||||||
|
// 15) Checksum
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
} else if (!strcmp(thisSentence, "DBK")) { //*****************************DBT
|
||||||
|
// DBK Depth Below Keel
|
||||||
|
// 1 2 3 4 5 6 7
|
||||||
|
// | | | | | | |
|
||||||
|
//$--DBK,x.x,f,x.x,M,x.x,F*hh
|
||||||
|
// 1) Depth, feet
|
||||||
|
// 2) f = feet
|
||||||
|
// 3) Depth, meters
|
||||||
|
// 4) M = meters
|
||||||
|
// 5) Depth, Fathoms
|
||||||
|
// 6) F = Fathoms
|
||||||
|
// 7) Checksum
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
} else if (!strcmp(thisSentence, "DBS")) { //*****************************DBT
|
||||||
|
// DBS Depth Below Surface
|
||||||
|
// 1 2 3 4 5 6 7
|
||||||
|
// | | | | | | |
|
||||||
|
//$--DBS,x.x,f,x.x,M,x.x,F*hh
|
||||||
|
// 1) Depth, feet
|
||||||
|
// 2) f = feet
|
||||||
|
// 3) Depth, meters
|
||||||
|
// 4) M = meters
|
||||||
|
// 5) Depth, Fathoms
|
||||||
|
// 6) F = Fathoms
|
||||||
|
// 7) Checksum
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
} else if (!strcmp(thisSentence, "DBT")) { //*****************************DBT
|
||||||
|
// DBT Depth Below Transducer
|
||||||
|
// 1 2 3 4 5 6 7
|
||||||
|
// | | | | | | |
|
||||||
|
//$--DBT,x.x,f,x.x,M,x.x,F*hh
|
||||||
|
// 1) Depth, feet
|
||||||
|
// 2) f = feet
|
||||||
|
// 3) Depth, meters
|
||||||
|
// 4) M = meters
|
||||||
|
// 5) Depth, Fathoms
|
||||||
|
// 6) F = Fathoms
|
||||||
|
// 7) Checksum
|
||||||
|
double d = val[NMEA_DEPTH].latest - depthToTransducer;
|
||||||
|
sprintf(p, "%f,f,%f,M,,,", d / 0.3048, d);
|
||||||
|
|
||||||
|
} else if (!strcmp(thisSentence, "DPT")) { //*****************************DPT
|
||||||
|
// DPT Heading – Deviation & Variation
|
||||||
|
// 1 2 3
|
||||||
|
// | | |
|
||||||
|
//$--DPT,x.x,x.x*hh
|
||||||
|
// 1) Depth, meters
|
||||||
|
// 2) Offset from transducer;
|
||||||
|
// positive means distance from transducer to water line,
|
||||||
|
// negative means distance from transducer to keel
|
||||||
|
// 3) Checksum
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
} else if (!strcmp(thisSentence, "GSV")) { //*****************************GSV
|
||||||
|
// GSV Satellites in view
|
||||||
|
// 1 2 3 4 5 6 7 n
|
||||||
|
// | | | | | | | |
|
||||||
|
//$--GSV,x,x,x,x,x,x,x,...*hh
|
||||||
|
// 1) total number of messages
|
||||||
|
// 2) message number
|
||||||
|
// 3) satellites in view
|
||||||
|
// 4) satellite number
|
||||||
|
// 5) elevation in degrees
|
||||||
|
// 6) azimuth in degrees to true
|
||||||
|
// 7) SNR in dB
|
||||||
|
// more satellite infos like 4)-7)
|
||||||
|
// n) Checksum
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
} else if (!strcmp(thisSentence, "HDG")) { //*****************************HDG
|
||||||
|
// HDG Heading – Deviation & Variation
|
||||||
|
// 1 2 3 4 5 6
|
||||||
|
// | | | | | |
|
||||||
|
//$--HDG,x.x,x.x,a,x.x,a*hh
|
||||||
|
// 1) Magnetic Sensor heading in degrees
|
||||||
|
// 2) Magnetic Deviation, degrees
|
||||||
|
// 3) Magnetic Deviation direction, E = Easterly, W = Westerly
|
||||||
|
// 4) Magnetic Variation degrees
|
||||||
|
// 5) Magnetic Variation direction, E = Easterly, W = Westerly
|
||||||
|
// 6) Checksum
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
} else if (!strcmp(thisSentence, "HDM")) { //*****************************HDM
|
||||||
|
// HDM Heading – Magnetic
|
||||||
|
// 1 2 3
|
||||||
|
// | | |
|
||||||
|
//$--HDM,x.x,M*hh
|
||||||
|
// 1) Heading Degrees, magnetic
|
||||||
|
// 2) M = magnetic
|
||||||
|
// 3) Checksum
|
||||||
|
sprintf(p, "%f,M", (double)val[NMEA_HDG].latest);
|
||||||
|
|
||||||
|
} else if (!strcmp(thisSentence, "HDT")) { //*****************************HDT
|
||||||
|
// HDT Heading – True
|
||||||
|
// 1 2 3
|
||||||
|
// | | |
|
||||||
|
//$--HDT,x.x,T*hh
|
||||||
|
// 1) Heading Degrees, true
|
||||||
|
// 2) T = True
|
||||||
|
// 3) Checksum
|
||||||
|
// starts with $II for integrated instrumentation
|
||||||
|
sprintf(p, "%f,T", (double)val[NMEA_HDT].latest);
|
||||||
|
|
||||||
|
} else if (!strcmp(thisSentence, "MDA")) { //*****************************MDA
|
||||||
|
// MDA Meteorological Composite
|
||||||
|
// 1 2 3 4 5 6 7 8 9 10 11 12
|
||||||
|
// | | | | | | | | | | | |
|
||||||
|
//$__MDA,x.x,I,x.x,B,x.x,C,x.x,C,x.x, ,x.x,C,,T,,M,,N,,M*hh
|
||||||
|
//$IIMDA,,I,,B,,C,21.8,C,,,,C,,T,,M,,N,,M*0F // sent by RayMarine i70s
|
||||||
|
// Speed/Depth/Wind
|
||||||
|
// 1) Barometric Pressure
|
||||||
|
// 2) inches of Hg
|
||||||
|
// 3) Barometric Pressure
|
||||||
|
// 4) bar
|
||||||
|
// 5) Atmospheric Temperature
|
||||||
|
// 6) C or F
|
||||||
|
// 7) Water Temperature
|
||||||
|
// 8) C or F
|
||||||
|
// 9) Relative Humidity
|
||||||
|
// 10)
|
||||||
|
// 11) Dew Point
|
||||||
|
// 12) C or F
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
} else if (!strcmp(thisSentence, "MTW")) { //*****************************MTW
|
||||||
|
// MTW Water Temperature
|
||||||
|
// 1 2 3
|
||||||
|
// | | |
|
||||||
|
//$IIMTW,x.x,C*hh
|
||||||
|
//$IIMTW,21.8,C*18 // sent by RayMarine i70s Speed/Depth/Wind
|
||||||
|
// 1) Degrees
|
||||||
|
// 2) Unit of Measurement, Celcius
|
||||||
|
// 3) Checksum
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
} else if (!strcmp(thisSentence, "MWD")) { //*****************************MWD
|
||||||
|
// MWD Wind Direction & Speed
|
||||||
|
// Format unknown
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
} else if (!strcmp(thisSentence, "MWV")) { //*****************************MWV
|
||||||
|
// MWV Wind Speed and Angle assuming values for True
|
||||||
|
// 1 2 3 4 5 6
|
||||||
|
// | | | | | |
|
||||||
|
//$IIMWV,x.x,a,x.x,a,a*hh
|
||||||
|
//$WIMWV,276.94,R,0,N,A*03 // sent by RayMarine i70s Speed/Depth/Wind
|
||||||
|
// 1) Wind Angle, 0 to 360 degrees
|
||||||
|
// 2) Reference, R = Relative, T = True
|
||||||
|
// 3) Wind Speed
|
||||||
|
// 4) Wind Speed Units, K/M/N kilometers/miles/knots
|
||||||
|
// 5) Status, A = Data Valid
|
||||||
|
// 6) Checksum
|
||||||
|
if (ref == 'R')
|
||||||
|
sprintf(p, "%f,%c,%f,N,A", (double)val[NMEA_AWA].latest, ref,
|
||||||
|
(double)val[NMEA_AWS].latest);
|
||||||
|
else
|
||||||
|
sprintf(p, "%f,%c,%f,N,A", (double)val[NMEA_TWA].latest, 'T',
|
||||||
|
(double)val[NMEA_TWS].latest);
|
||||||
|
|
||||||
|
} else if (!strcmp(thisSentence, "RMB")) { //*****************************RMB
|
||||||
|
// RMB Recommended Minimum Navigation Information
|
||||||
|
// 1 2 3 4 5 6 7 8 9 10 11 12 13 14
|
||||||
|
// | | | | | | | | | | | | | |
|
||||||
|
//$--RMB,A,x.x,a,c--c,c--c,llll.ll,a,yyyyy.yy,a,x.x,x.x,x.x,A*hh
|
||||||
|
// 1) Status, V = Navigation receiver warning
|
||||||
|
// 2) Cross Track error - nautical miles
|
||||||
|
// 3) Direction to Steer, Left or Right
|
||||||
|
// 4) TO Waypoint ID
|
||||||
|
// 5) FROM Waypoint ID
|
||||||
|
// 6) Destination Waypoint Latitude 7) N or S
|
||||||
|
// 8) Destination Waypoint Longitude 9) E or W
|
||||||
|
// 10) Range to destination in nautical miles
|
||||||
|
// 11) Bearing to destination in degrees True
|
||||||
|
// 12) Destination closing velocity in knots
|
||||||
|
// 13) Arrival Status, A = Arrival Circle Entered 14) Checksum
|
||||||
|
sprintf(p, ",,,,,,,,,,,%f,A", (double)val[NMEA_VMGWP].latest);
|
||||||
|
|
||||||
|
} else if (!strcmp(thisSentence, "ROT")) { //*****************************ROT
|
||||||
|
// ROT Rate Of Turn
|
||||||
|
// 1 2 3
|
||||||
|
// | | |
|
||||||
|
//$--ROT,x.x,A*hh
|
||||||
|
// 1) Rate Of Turn, degrees per minute, "-" means bow turns to port
|
||||||
|
// 2) Status, A means data is valid
|
||||||
|
// 3) Checksum
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
} else if (!strcmp(thisSentence, "RPM")) { //*****************************RPM
|
||||||
|
// RPM Revolutions
|
||||||
|
// 1 2 3 4 5 6
|
||||||
|
// | | | | | |
|
||||||
|
//$--RPM,a,x,x.x,x.x,A*hh
|
||||||
|
// 1) Source; S = Shaft, E = Engine
|
||||||
|
// 2) Engine or shaft number
|
||||||
|
// 3) Speed, Revolutions per minute
|
||||||
|
// 4) Propeller pitch, % of maximum, "-" means astern
|
||||||
|
// 5) Status, A means data is valid
|
||||||
|
// 6) Checksum
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
} else if (!strcmp(thisSentence, "RSA")) { //*****************************RSA
|
||||||
|
// RSA Rudder Sensor Angle
|
||||||
|
// 1 2 3 4 5
|
||||||
|
// | | | | |
|
||||||
|
//$--RSA,x.x,A,x.x,A*hh
|
||||||
|
// 1) Starboard (or single) rudder sensor, "-" means Turn To Port
|
||||||
|
// 2) Status, A means data is valid
|
||||||
|
// 3) Port rudder sensor
|
||||||
|
// 4) Status, A means data is valid
|
||||||
|
// 5) Checksum
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
} else if (!strcmp(thisSentence, "TXT")) { //*****************************TXT
|
||||||
// as mentioned in https://github.com/adafruit/Adafruit_GPS/issues/95
|
// as mentioned in https://github.com/adafruit/Adafruit_GPS/issues/95
|
||||||
// TXT Text Transmission
|
// TXT Text Transmission
|
||||||
// 1 2 3 4 5
|
// 1 2 3 4 5
|
||||||
|
|
@ -185,6 +434,136 @@ char *Adafruit_GPS::build(char *nmea, const char *thisSource,
|
||||||
// 5) Checksum
|
// 5) Checksum
|
||||||
sprintf(p, "01,01,23,This is the text of the sample message");
|
sprintf(p, "01,01,23,This is the text of the sample message");
|
||||||
|
|
||||||
|
} else if (!strcmp(thisSentence, "VDR")) { //*****************************VDR
|
||||||
|
// VDR Set and Drift
|
||||||
|
// 1 2 3 4 5 6 7
|
||||||
|
// | | | | | | |
|
||||||
|
//$--VDR,x.x,T,x.x,M,x.x,N*hh
|
||||||
|
// 1) Degress True
|
||||||
|
// 2) T = True
|
||||||
|
// 3) Degrees Magnetic
|
||||||
|
// 4) M = Magnetic
|
||||||
|
// 5) Knots (speed of current)
|
||||||
|
// 6) N = Knots
|
||||||
|
// 7) Checksum
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
} else if (!strcmp(thisSentence, "VHW")) { //*****************************VHW
|
||||||
|
// VHW Water Speed and Heading
|
||||||
|
// 1 2 3 4 5 6 7 8 9
|
||||||
|
// | | | | | | | | |
|
||||||
|
//$--VHW,x.x,T,x.x,M,x.x,N,x.x,K*hh
|
||||||
|
//$IIVHW,,T,,M,0,N,0,K*55 // sent by RayMarine i70s Speed/Depth/Wind
|
||||||
|
// 1) Degrees True
|
||||||
|
// 2) T = True
|
||||||
|
// 3) Degrees Magnetic
|
||||||
|
// 4) M = Magnetic
|
||||||
|
// 5) Knots (speed of vessel relative to the water) [66]
|
||||||
|
// 6) N = Knots
|
||||||
|
// 7) Kilometers (speed of vessel relative to the water)
|
||||||
|
// 8) K = Kilometres
|
||||||
|
// 9) Checksum
|
||||||
|
sprintf(p, "%f,T,%f,M,%f,N,%f,K", (double)val[NMEA_HDT].latest,
|
||||||
|
(double)val[NMEA_HDG].latest, (double)val[NMEA_VTW].latest,
|
||||||
|
(double)val[NMEA_VTW].latest * 1.829);
|
||||||
|
|
||||||
|
} else if (!strcmp(thisSentence, "VLW")) { //*****************************VLW
|
||||||
|
// VLW Distance Traveled through Water
|
||||||
|
// 1 2 3 4 5
|
||||||
|
// | | | | |
|
||||||
|
//$--VLW,x.x,N,x.x,N*hh
|
||||||
|
//$IIVLW,0,N,0,N,,N,,N*4D // sent by RayMarine i70s Speed/Depth/Wind
|
||||||
|
// not sure what the last two are?
|
||||||
|
// 1) Total cumulative distance
|
||||||
|
// 2) N = Nautical Miles
|
||||||
|
// 3) Distance since Reset
|
||||||
|
// 4) N = Nautical Miles
|
||||||
|
// 5) Checksum
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
} else if (!strcmp(thisSentence, "VPW")) { //*****************************VPW
|
||||||
|
// not supported by iNavX
|
||||||
|
// VPW Speed – Measured Parallel to Wind
|
||||||
|
// 1 2 3 4 5
|
||||||
|
// | | | | |
|
||||||
|
//$--VPW,x.x,N,x.x,M*hh
|
||||||
|
// 1) Speed, "-" means downwind
|
||||||
|
// 2) N = Knots
|
||||||
|
// 3) Speed, "-" means downwind
|
||||||
|
// 4) M = Meters per second
|
||||||
|
// 5) Checksum
|
||||||
|
sprintf(p, "%f,N,,", (double)val[NMEA_VMG].latest);
|
||||||
|
|
||||||
|
} else if (!strcmp(thisSentence, "VTG")) { //*****************************VTG
|
||||||
|
// VTG Track Made Good and Ground Speed
|
||||||
|
// 1 2 3 4 5 6 7 8 9
|
||||||
|
// | | | | | | | | |
|
||||||
|
//$--VTG,x.x,T,x.x,M,x.x,N,x.x,K*hh
|
||||||
|
// 1) Track Degrees 2) T = True
|
||||||
|
// 3) Track Degrees 4) M = Magnetic
|
||||||
|
// 5) Speed Knots 6) N = Knots
|
||||||
|
// 7) Speed Kilometers Per Hour 8) K = Kilometres Per Hour
|
||||||
|
// 9) Checksum
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
} else if (!strcmp(thisSentence, "VWR")) { //*****************************VWR
|
||||||
|
// VWR Relative Wind Speed and Angle
|
||||||
|
// 1 2 3 4 5 6 7 8 9
|
||||||
|
// | | | | | | | | |
|
||||||
|
//$--VWR,x.x,a,x.x,N,x.x,M,x.x,K*hh
|
||||||
|
//$WIVWR,83.1,L,0,N,0,M,0,K*6D // sent by RayMarine i70s
|
||||||
|
// Speed/Depth/Wind
|
||||||
|
// 1) Wind direction magnitude in degrees
|
||||||
|
// 2) Wind direction Left/Right of bow
|
||||||
|
// 3) Speed
|
||||||
|
// 4) N = Knots
|
||||||
|
// 5) Speed
|
||||||
|
// 6) M = Meters Per Second
|
||||||
|
// 7) Speed
|
||||||
|
// 8) K = Kilometers Per Hour
|
||||||
|
// 9) Checksum
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
} else if (!strcmp(thisSentence, "WCV")) { //*****************************WCV
|
||||||
|
// WCV Waypoint Closure Velocity
|
||||||
|
// 1 2 3 4
|
||||||
|
// | | | |
|
||||||
|
//$--WCV,x.x,N,c--c*hh
|
||||||
|
// 1) Velocity 2) N = knots 3) Waypoint ID 4) Checksum
|
||||||
|
sprintf(p, "%f,N,home", (double)val[NMEA_VMG].latest);
|
||||||
|
|
||||||
|
} else if (!strcmp(thisSentence, "XTE")) { //*****************************XTE
|
||||||
|
// XTE Cross-Track Error – Measured
|
||||||
|
// 1 2 3 4 5 6
|
||||||
|
// | | | | | |
|
||||||
|
//$--XTE,A,A,x.x,a,N,*hh
|
||||||
|
// 1) Status
|
||||||
|
// V = LORAN-C blink or SNR warning
|
||||||
|
// A = general warning flag or other navigation systems when a reliable
|
||||||
|
// fix is not available
|
||||||
|
// 2) Status
|
||||||
|
// V = Loran-C cycle lock warning flag
|
||||||
|
// A = OK or not used
|
||||||
|
// 3) Cross track error magnitude
|
||||||
|
// 4) Direction to steer, L or R
|
||||||
|
// 5) Cross track units. N = Nautical Miles
|
||||||
|
// 6) Checksum
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
} else if (!strcmp(thisSentence, "ZDA")) { //*****************************ZDA
|
||||||
|
// ZDA Time & Date – UTC, Day, Month, Year and Local Time Zone
|
||||||
|
// 1 2 3 4 5 6 7
|
||||||
|
// | | | | | | |
|
||||||
|
//$--ZDA,hhmmss.ss,xx,xx,xxxx,xx,xx*hh
|
||||||
|
// 1) Local zone minutes description, same sign as local hours
|
||||||
|
// 2) Local zone description, 00 to +/- 13 hours
|
||||||
|
// 3) Year
|
||||||
|
// 4) Month, 01 to 12
|
||||||
|
// 5) Day, 01 to 31
|
||||||
|
// 6) Time (UTC)
|
||||||
|
// 7) Checksum
|
||||||
|
return NULL;
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
return NULL; // didn't find a match for the build request
|
return NULL; // didn't find a match for the build request
|
||||||
}
|
}
|
||||||
|
|
@ -196,3 +575,24 @@ char *Adafruit_GPS::build(char *nmea, const char *thisSource,
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif // NMEA_EXTENSIONS
|
#endif // NMEA_EXTENSIONS
|
||||||
|
|
||||||
|
/**************************************************************************/
|
||||||
|
/*!
|
||||||
|
@brief Add *CS where CS is the two character hex checksum for all but
|
||||||
|
the first character in the string. The checksum is the result of an
|
||||||
|
exclusive or of all the characters in the string. Also useful if you
|
||||||
|
are creating new PMTK strings for controlling a GPS module and need a
|
||||||
|
checksum added.
|
||||||
|
@param buff Pointer to the string, which must be long enough
|
||||||
|
@return none
|
||||||
|
*/
|
||||||
|
/**************************************************************************/
|
||||||
|
void Adafruit_GPS::addChecksum(char *buff) {
|
||||||
|
char cs = 0;
|
||||||
|
int i = 1;
|
||||||
|
while (buff[i]) {
|
||||||
|
cs ^= buff[i];
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
sprintf(buff, "%s*%02X", buff, cs);
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
@param nmea Pointer to the NMEA string
|
||||||
@return True if we parsed it, false if it has an invalid checksum or invalid
|
@return True if successfully parsed, false if fails check or parsing
|
||||||
data
|
|
||||||
*/
|
*/
|
||||||
/**************************************************************************/
|
/**************************************************************************/
|
||||||
bool Adafruit_GPS::parse(char *nmea) {
|
bool Adafruit_GPS::parse(char *nmea) {
|
||||||
// do checksum check
|
|
||||||
if (!check(nmea))
|
if (!check(nmea))
|
||||||
return false;
|
return false;
|
||||||
// passed the check, so there's a valid source in thisSource and a valid
|
// passed the check, so there's a valid source in thisSource and a valid
|
||||||
// sentence in thisSentence
|
// sentence in thisSentence
|
||||||
|
|
||||||
// look for a few common sentences
|
|
||||||
char *p = nmea; // Pointer to move through the sentence -- good parsers are
|
char *p = nmea; // Pointer to move through the sentence -- good parsers are
|
||||||
// non-destructive
|
// non-destructive
|
||||||
p = strchr(p, ',') +
|
p = strchr(p, ',') + 1; // Skip to char after the next comma, then check.
|
||||||
1; // Skip to the character after the next comma, then check sentence.
|
|
||||||
|
|
||||||
if (!strcmp(thisSentence, "GGA")) {
|
// This may look inefficient, but an M0 will get down the list in about 1 us /
|
||||||
// found GGA
|
// strcmp()! Put the GPS sentences from Adafruit_GPS at the top to make
|
||||||
// get time
|
// pruning excess code easier. Otherwise, keep them alphabetical for ease of
|
||||||
|
// reading.
|
||||||
|
if (!strcmp(thisSentence, "GGA")) { //************************************GGA
|
||||||
|
// Adafruit from Actisense NGW-1 from SH CP150C
|
||||||
parseTime(p);
|
parseTime(p);
|
||||||
|
p = strchr(p, ',') + 1; // parse time with specialized function
|
||||||
// parse out latitude
|
// parse out both latitude and direction, then go to next field, or fail
|
||||||
|
if (parseCoord(p, &latitudeDegrees, &latitude, &latitude_fixed, &lat))
|
||||||
|
newDataValue(NMEA_LAT, latitudeDegrees);
|
||||||
p = strchr(p, ',') + 1;
|
p = strchr(p, ',') + 1;
|
||||||
parseLat(p);
|
|
||||||
p = strchr(p, ',') + 1;
|
p = strchr(p, ',') + 1;
|
||||||
if (!parseLatDir(p))
|
// parse out both longitude and direction, then go to next field, or fail
|
||||||
return false;
|
if (parseCoord(p, &longitudeDegrees, &longitude, &longitude_fixed, &lon))
|
||||||
|
newDataValue(NMEA_LON, longitudeDegrees);
|
||||||
// parse out longitude
|
|
||||||
p = strchr(p, ',') + 1;
|
p = strchr(p, ',') + 1;
|
||||||
parseLon(p);
|
|
||||||
p = strchr(p, ',') + 1;
|
p = strchr(p, ',') + 1;
|
||||||
if (!parseLonDir(p))
|
if (!isEmpty(p)) { // if it's a , (or a * at end of sentence) the value is
|
||||||
return false;
|
// not included
|
||||||
|
fixquality = atoi(p); // needs additional processing
|
||||||
p = strchr(p, ',') + 1;
|
|
||||||
if (!isEmpty(p)) {
|
|
||||||
fixquality = atoi(p);
|
|
||||||
if (fixquality > 0) {
|
if (fixquality > 0) {
|
||||||
fix = true;
|
fix = true;
|
||||||
lastFix = sentTime;
|
lastFix = sentTime;
|
||||||
} else
|
} else
|
||||||
fix = false;
|
fix = false;
|
||||||
}
|
}
|
||||||
|
p = strchr(p, ',') + 1; // then move on to the next
|
||||||
p = strchr(p, ',') + 1;
|
// Most can just be parsed with atoi() or atof(), then move on to the next.
|
||||||
if (!isEmpty(p)) {
|
if (!isEmpty(p))
|
||||||
satellites = atoi(p);
|
satellites = atoi(p);
|
||||||
}
|
|
||||||
|
|
||||||
p = strchr(p, ',') + 1;
|
p = strchr(p, ',') + 1;
|
||||||
if (!isEmpty(p)) {
|
if (!isEmpty(p))
|
||||||
HDOP = atof(p);
|
newDataValue(NMEA_HDOP, HDOP = atof(p));
|
||||||
}
|
|
||||||
|
|
||||||
p = strchr(p, ',') + 1;
|
p = strchr(p, ',') + 1;
|
||||||
if (!isEmpty(p)) {
|
if (!isEmpty(p))
|
||||||
altitude = atof(p);
|
altitude = atof(p);
|
||||||
}
|
|
||||||
|
|
||||||
p = strchr(p, ',') + 1;
|
p = strchr(p, ',') + 1;
|
||||||
p = strchr(p, ',') + 1;
|
p = strchr(p, ',') + 1; // skip the units
|
||||||
if (!isEmpty(p)) {
|
if (!isEmpty(p))
|
||||||
geoidheight = atof(p);
|
geoidheight = atof(p); // skip the rest
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
else if (!strcmp(thisSentence, "RMC")) {
|
} else if (!strcmp(thisSentence, "RMC")) { //*****************************RMC
|
||||||
// found RMC
|
// in Adafruit from Actisense NGW-1 from SH CP150C
|
||||||
// get time
|
|
||||||
parseTime(p);
|
parseTime(p);
|
||||||
|
|
||||||
// fix or no fix
|
|
||||||
p = strchr(p, ',') + 1;
|
p = strchr(p, ',') + 1;
|
||||||
if (!parseFix(p))
|
parseFix(p);
|
||||||
return false;
|
|
||||||
|
|
||||||
// parse out latitude
|
|
||||||
p = strchr(p, ',') + 1;
|
p = strchr(p, ',') + 1;
|
||||||
parseLat(p);
|
// parse out both latitude and direction, then go to next field, or fail
|
||||||
|
if (parseCoord(p, &latitudeDegrees, &latitude, &latitude_fixed, &lat))
|
||||||
|
newDataValue(NMEA_LAT, latitudeDegrees);
|
||||||
p = strchr(p, ',') + 1;
|
p = strchr(p, ',') + 1;
|
||||||
if (!parseLatDir(p))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
// parse out longitude
|
|
||||||
p = strchr(p, ',') + 1;
|
p = strchr(p, ',') + 1;
|
||||||
parseLon(p);
|
// parse out both longitude and direction, then go to next field, or fail
|
||||||
|
if (parseCoord(p, &longitudeDegrees, &longitude, &longitude_fixed, &lon))
|
||||||
|
newDataValue(NMEA_LON, longitudeDegrees);
|
||||||
p = strchr(p, ',') + 1;
|
p = strchr(p, ',') + 1;
|
||||||
if (!parseLonDir(p))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
// speed
|
|
||||||
p = strchr(p, ',') + 1;
|
p = strchr(p, ',') + 1;
|
||||||
if (!isEmpty(p)) {
|
if (!isEmpty(p))
|
||||||
speed = atof(p);
|
newDataValue(NMEA_SOG, speed = atof(p));
|
||||||
}
|
|
||||||
|
|
||||||
// angle
|
|
||||||
p = strchr(p, ',') + 1;
|
p = strchr(p, ',') + 1;
|
||||||
if (!isEmpty(p)) {
|
if (!isEmpty(p))
|
||||||
angle = atof(p);
|
newDataValue(NMEA_COG, angle = atof(p));
|
||||||
}
|
|
||||||
|
|
||||||
p = strchr(p, ',') + 1;
|
p = strchr(p, ',') + 1;
|
||||||
if (!isEmpty(p)) {
|
if (!isEmpty(p)) {
|
||||||
uint32_t fulldate = atof(p);
|
uint32_t fulldate = atof(p);
|
||||||
|
|
@ -145,66 +135,264 @@ bool Adafruit_GPS::parse(char *nmea) {
|
||||||
month = (fulldate % 10000) / 100;
|
month = (fulldate % 10000) / 100;
|
||||||
year = (fulldate % 100);
|
year = (fulldate % 100);
|
||||||
lastDate = sentTime;
|
lastDate = sentTime;
|
||||||
}
|
} // skip the rest
|
||||||
}
|
|
||||||
|
|
||||||
else if (!strcmp(thisSentence, "GLL")) {
|
} else if (!strcmp(thisSentence, "GLL")) { //*****************************GLL
|
||||||
// found GLL
|
// in Adafruit from Actisense NGW-1 from SH CP150C
|
||||||
// parse out latitude
|
// parse out both latitude and direction, then go to next field, or fail
|
||||||
parseLat(p);
|
if (parseCoord(p, &latitudeDegrees, &latitude, &latitude_fixed, &lat))
|
||||||
|
newDataValue(NMEA_LAT, latitudeDegrees);
|
||||||
p = strchr(p, ',') + 1;
|
p = strchr(p, ',') + 1;
|
||||||
if (!parseLatDir(p))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
// parse out longitude
|
|
||||||
p = strchr(p, ',') + 1;
|
p = strchr(p, ',') + 1;
|
||||||
parseLon(p);
|
// parse out both longitude and direction, then go to next field, or fail
|
||||||
|
if (parseCoord(p, &longitudeDegrees, &longitude, &longitude_fixed, &lon))
|
||||||
|
newDataValue(NMEA_LON, longitudeDegrees);
|
||||||
p = strchr(p, ',') + 1;
|
p = strchr(p, ',') + 1;
|
||||||
if (!parseLonDir(p))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
// get time
|
|
||||||
p = strchr(p, ',') + 1;
|
p = strchr(p, ',') + 1;
|
||||||
parseTime(p);
|
parseTime(p);
|
||||||
|
|
||||||
// fix or no fix
|
|
||||||
p = strchr(p, ',') + 1;
|
p = strchr(p, ',') + 1;
|
||||||
if (!parseFix(p))
|
parseFix(p); // skip the rest
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
else if (!strcmp(thisSentence, "GSA")) {
|
} else if (!strcmp(thisSentence, "GSA")) { //*****************************GSA
|
||||||
// found GSA
|
// in Adafruit from Actisense NGW-1
|
||||||
// parse out Auto selection, but ignore them
|
p = strchr(p, ',') + 1; // skip selection mode
|
||||||
// parse out 3d fixquality
|
if (!isEmpty(p))
|
||||||
p = strchr(p, ',') + 1;
|
|
||||||
if (!isEmpty(p)) {
|
|
||||||
fixquality_3d = atoi(p);
|
fixquality_3d = atoi(p);
|
||||||
}
|
p = strchr(p, ',') + 1;
|
||||||
// skip 12 Satellite PDNs without interpreting them
|
// skip 12 Satellite PDNs without interpreting them
|
||||||
for (int i = 0; i < 12; i++)
|
for (int i = 0; i < 12; i++)
|
||||||
p = strchr(p, ',') + 1;
|
p = strchr(p, ',') + 1;
|
||||||
|
if (!isEmpty(p))
|
||||||
// parse out PDOP
|
|
||||||
p = strchr(p, ',') + 1;
|
|
||||||
if (!isEmpty(p)) {
|
|
||||||
PDOP = atof(p);
|
PDOP = atof(p);
|
||||||
}
|
p = strchr(p, ',') + 1;
|
||||||
// parse out HDOP, we also parse this from the GGA sentence. Chipset should
|
// parse out HDOP, we also parse this from the GGA sentence. Chipset should
|
||||||
// report the same for both
|
// report the same for both
|
||||||
|
if (!isEmpty(p))
|
||||||
|
newDataValue(NMEA_HDOP, HDOP = atof(p));
|
||||||
p = strchr(p, ',') + 1;
|
p = strchr(p, ',') + 1;
|
||||||
if (!isEmpty(p)) {
|
if (!isEmpty(p))
|
||||||
HDOP = atof(p);
|
VDOP = atof(p); // last before checksum
|
||||||
}
|
|
||||||
// parse out VDOP
|
|
||||||
p = strchr(p, ',') + 1;
|
|
||||||
if (!isEmpty(p)) {
|
|
||||||
VDOP = atof(p);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
}
|
||||||
#ifdef NMEA_EXTENSIONS // Sentences not required for basic GPS functionality
|
#ifdef NMEA_EXTENSIONS // Sentences not required for basic GPS functionality
|
||||||
else if (!strcmp(thisSentence, "TXT")) { //*******************************TXT
|
else if (!strcmp(thisSentence, "APB")) { //*******************************APB
|
||||||
|
// from Actisense NGW-1 from SH CP150C
|
||||||
|
return false;
|
||||||
|
|
||||||
|
} else if (!strcmp(thisSentence, "DBT")) { //*****************************DBT
|
||||||
|
// from Actisense NGW-1
|
||||||
|
// feet, metres, fathoms below transducer coerced to water depth from
|
||||||
|
// surface in metres
|
||||||
|
if (!isEmpty(p))
|
||||||
|
newDataValue(NMEA_DEPTH, atof(p) * 0.3048 + depthToTransducer);
|
||||||
|
p = strchr(p, ',') + 1;
|
||||||
|
p = strchr(p, ',') + 1;
|
||||||
|
if (!isEmpty(p))
|
||||||
|
newDataValue(NMEA_DEPTH, atof(p) + depthToTransducer);
|
||||||
|
p = strchr(p, ',') + 1;
|
||||||
|
p = strchr(p, ',') + 1;
|
||||||
|
if (!isEmpty(p))
|
||||||
|
newDataValue(NMEA_DEPTH, atof(p) * 6 * 0.3048 + depthToTransducer);
|
||||||
|
|
||||||
|
} else if (!strcmp(thisSentence, "DPT")) { //*****************************DPT
|
||||||
|
// from Actisense NGW-1
|
||||||
|
return false;
|
||||||
|
|
||||||
|
} else if (!strcmp(thisSentence, "GSV")) { //*****************************GSV
|
||||||
|
// from Actisense NGW-1
|
||||||
|
return false;
|
||||||
|
|
||||||
|
} else if (!strcmp(thisSentence, "HDG")) { //*****************************HDG
|
||||||
|
// from Actisense NGW-1 from SH CP150C
|
||||||
|
return false;
|
||||||
|
|
||||||
|
} else if (!strcmp(thisSentence, "HDM")) { //*****************************HDM
|
||||||
|
if (!isEmpty(p))
|
||||||
|
newDataValue(NMEA_HDG, atof(p)); // skip the rest
|
||||||
|
|
||||||
|
} else if (!strcmp(thisSentence, "HDT")) { //*****************************HDT
|
||||||
|
if (!isEmpty(p))
|
||||||
|
newDataValue(NMEA_HDT, atof(p)); // skip the rest
|
||||||
|
|
||||||
|
} else if (!strcmp(thisSentence, "MDA")) { //*****************************MDA
|
||||||
|
// from Actisense NGW-1
|
||||||
|
if (!isEmpty(p))
|
||||||
|
newDataValue(NMEA_BAROMETER, atof(p) * 3386.39);
|
||||||
|
p = strchr(p, ',') + 1;
|
||||||
|
p = strchr(p, ',') + 1;
|
||||||
|
if (!isEmpty(p))
|
||||||
|
newDataValue(NMEA_BAROMETER, atof(p) * 100000);
|
||||||
|
p = strchr(p, ',') + 1;
|
||||||
|
p = strchr(p, ',') + 1;
|
||||||
|
nmea_float_t T = 100000.;
|
||||||
|
char u = 'C';
|
||||||
|
if (!isEmpty(p))
|
||||||
|
T = atof(p);
|
||||||
|
p = strchr(p, ',') + 1;
|
||||||
|
if (!isEmpty(p))
|
||||||
|
u = *p;
|
||||||
|
p = strchr(p, ',') + 1;
|
||||||
|
if (u != 'C') {
|
||||||
|
T = (T - 32) / 1.8;
|
||||||
|
u = 'C';
|
||||||
|
} // coerce to C
|
||||||
|
if (T < 1000)
|
||||||
|
newDataValue(NMEA_TEMPERATURE_AIR, T);
|
||||||
|
T = 100000.;
|
||||||
|
u = 'C';
|
||||||
|
if (!isEmpty(p))
|
||||||
|
T = atof(p);
|
||||||
|
p = strchr(p, ',') + 1;
|
||||||
|
if (!isEmpty(p))
|
||||||
|
u = *p;
|
||||||
|
p = strchr(p, ',') + 1;
|
||||||
|
if (u != 'C') {
|
||||||
|
T = (T - 32) / 1.8;
|
||||||
|
u = 'C';
|
||||||
|
}
|
||||||
|
if (T < 1000)
|
||||||
|
newDataValue(NMEA_TEMPERATURE_WATER, T);
|
||||||
|
if (!isEmpty(p))
|
||||||
|
newDataValue(NMEA_HUMIDITY, atof(p)); // skip the rest
|
||||||
|
|
||||||
|
} else if (!strcmp(thisSentence, "MTW")) { //*****************************MTW
|
||||||
|
nmea_float_t T = 100000.;
|
||||||
|
char u = 'C';
|
||||||
|
if (!isEmpty(p))
|
||||||
|
T = atof(p);
|
||||||
|
p = strchr(p, ',') + 1;
|
||||||
|
if (!isEmpty(p))
|
||||||
|
u = *p; // last before checksum
|
||||||
|
if (u != 'C') {
|
||||||
|
T = (T - 32) / 1.8;
|
||||||
|
u = 'C';
|
||||||
|
}
|
||||||
|
if (T < 1000)
|
||||||
|
newDataValue(NMEA_TEMPERATURE_WATER, T);
|
||||||
|
|
||||||
|
} else if (!strcmp(thisSentence, "MWD")) { //*****************************MWD
|
||||||
|
// from Actisense NGW-1
|
||||||
|
return false;
|
||||||
|
|
||||||
|
} else if (!strcmp(thisSentence, "MWV")) { //*****************************MWV
|
||||||
|
// from Actisense NGW-1
|
||||||
|
nmea_float_t ang = 100000.;
|
||||||
|
char ref = 'T';
|
||||||
|
if (!isEmpty(p))
|
||||||
|
ang = atof(p);
|
||||||
|
p = strchr(p, ',') + 1;
|
||||||
|
if (!isEmpty(p))
|
||||||
|
ref = *p;
|
||||||
|
p = strchr(p, ',') + 1;
|
||||||
|
nmea_float_t spd = 100000.;
|
||||||
|
if (!isEmpty(p))
|
||||||
|
spd = atof(p);
|
||||||
|
p = strchr(p, ',') + 1;
|
||||||
|
char units = 'N';
|
||||||
|
if (!isEmpty(p))
|
||||||
|
units = *p;
|
||||||
|
p = strchr(p, ',') + 1;
|
||||||
|
char stat = 'A';
|
||||||
|
if (!isEmpty(p))
|
||||||
|
stat = *p; // last before checksum
|
||||||
|
if (units == 'K') {
|
||||||
|
spd /= 1.6;
|
||||||
|
units = 'M';
|
||||||
|
}
|
||||||
|
if (units == 'M') {
|
||||||
|
spd *= 5280. / 6000.;
|
||||||
|
units = 'N';
|
||||||
|
}
|
||||||
|
if (ang > 180.)
|
||||||
|
ang -= 360.;
|
||||||
|
if (ref == 'R') {
|
||||||
|
if (ang < 1000. && stat == 'A')
|
||||||
|
newDataValue(NMEA_AWA, ang);
|
||||||
|
if (spd < 1000. && stat == 'A')
|
||||||
|
newDataValue(NMEA_AWS, spd);
|
||||||
|
} else {
|
||||||
|
if (ang < 1000. && stat == 'A')
|
||||||
|
newDataValue(NMEA_TWA, ang);
|
||||||
|
if (spd < 1000. && stat == 'A')
|
||||||
|
newDataValue(NMEA_TWS, spd);
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if (!strcmp(thisSentence, "RMB")) { //*****************************RMB
|
||||||
|
// from Actisense NGW-1 from SH CP150C
|
||||||
|
p = strchr(p, ',') + 1; // skip status
|
||||||
|
nmea_float_t xte = 100000.;
|
||||||
|
char xteDir = 'X';
|
||||||
|
if (!isEmpty(p))
|
||||||
|
xte = atof(p);
|
||||||
|
p = strchr(p, ',') + 1;
|
||||||
|
if (!isEmpty(p))
|
||||||
|
xteDir = *p;
|
||||||
|
p = strchr(p, ',') + 1;
|
||||||
|
if (xte < 10000. && xteDir != 'X') {
|
||||||
|
if (xteDir == 'L')
|
||||||
|
xte *= -1.;
|
||||||
|
newDataValue(NMEA_XTE, xte);
|
||||||
|
}
|
||||||
|
if (!isEmpty(p))
|
||||||
|
parseStr(toID, p, NMEA_MAX_WP_ID);
|
||||||
|
p = strchr(p, ',') + 1;
|
||||||
|
if (!isEmpty(p))
|
||||||
|
parseStr(fromID, p, NMEA_MAX_WP_ID);
|
||||||
|
p = strchr(p, ',') + 1;
|
||||||
|
nmea_float_t latitudeWP =
|
||||||
|
0; // All the same position data for the next way point
|
||||||
|
nmea_float_t longitudeWP = 0;
|
||||||
|
int32_t latitude_fixedWP = 0;
|
||||||
|
int32_t longitude_fixedWP = 0;
|
||||||
|
nmea_float_t latitudeDegreesWP = 0;
|
||||||
|
nmea_float_t longitudeDegreesWP = 0;
|
||||||
|
char latWP = 'N';
|
||||||
|
char lonWP = 'W';
|
||||||
|
|
||||||
|
// parse out both latitude and direction for WayPoint, then go to next
|
||||||
|
// field, or fail
|
||||||
|
if (!isEmpty(p)) {
|
||||||
|
if (!parseCoord(p, &latitudeDegreesWP, &latitudeWP, &latitude_fixedWP,
|
||||||
|
&latWP))
|
||||||
|
return false;
|
||||||
|
else
|
||||||
|
newDataValue(NMEA_LATWP, latitudeDegreesWP);
|
||||||
|
}
|
||||||
|
p = strchr(p, ',') + 1;
|
||||||
|
p = strchr(p, ',') + 1;
|
||||||
|
// parse out both longitude and direction for WayPoint, then go to next
|
||||||
|
// field, or fail
|
||||||
|
if (!isEmpty(p)) {
|
||||||
|
if (!parseCoord(p, &longitudeDegreesWP, &longitudeWP, &longitude_fixedWP,
|
||||||
|
&lonWP))
|
||||||
|
return false;
|
||||||
|
else
|
||||||
|
newDataValue(NMEA_LONWP, longitudeDegreesWP);
|
||||||
|
}
|
||||||
|
p = strchr(p, ',') + 1;
|
||||||
|
p = strchr(p, ',') + 1;
|
||||||
|
p = strchr(p, ',') + 1;
|
||||||
|
p = strchr(p, ',') + 1;
|
||||||
|
if (!isEmpty(p))
|
||||||
|
newDataValue(NMEA_DISTWP, atof(p));
|
||||||
|
p = strchr(p, ',') + 1;
|
||||||
|
if (!isEmpty(p))
|
||||||
|
newDataValue(NMEA_COGWP, atof(p));
|
||||||
|
p = strchr(p, ',') + 1;
|
||||||
|
if (!isEmpty(p))
|
||||||
|
newDataValue(NMEA_VMGWP, atof(p)); // skip arrival flag
|
||||||
|
|
||||||
|
} else if (!strcmp(thisSentence, "ROT")) { //*****************************ROT
|
||||||
|
return false;
|
||||||
|
|
||||||
|
} else if (!strcmp(thisSentence, "RPM")) { //*****************************RPM
|
||||||
|
return false;
|
||||||
|
|
||||||
|
} else if (!strcmp(thisSentence, "RSA")) { //*****************************RSA
|
||||||
|
// from Actisense NGW-1
|
||||||
|
return false;
|
||||||
|
|
||||||
|
} else if (!strcmp(thisSentence, "TXT")) { //*****************************TXT
|
||||||
if (!isEmpty(p))
|
if (!isEmpty(p))
|
||||||
txtTot = atoi(p);
|
txtTot = atoi(p);
|
||||||
p = strchr(p, ',') + 1;
|
p = strchr(p, ',') + 1;
|
||||||
|
|
@ -216,12 +404,128 @@ bool Adafruit_GPS::parse(char *nmea) {
|
||||||
p = strchr(p, ',') + 1;
|
p = strchr(p, ',') + 1;
|
||||||
if (!isEmpty(p))
|
if (!isEmpty(p))
|
||||||
parseStr(txtTXT, p, 61); // copy the text to NMEA TXT max of 61 characters
|
parseStr(txtTXT, p, 61); // copy the text to NMEA TXT max of 61 characters
|
||||||
|
|
||||||
|
} else if (!strcmp(thisSentence, "VDR")) { //*****************************VDR
|
||||||
|
// from Actisense NGW-1
|
||||||
|
return false;
|
||||||
|
|
||||||
|
} else if (!strcmp(thisSentence, "VHW")) { //*****************************VHW
|
||||||
|
// from Actisense NGW-1
|
||||||
|
if (!isEmpty(p))
|
||||||
|
newDataValue(NMEA_HDT, atof(p));
|
||||||
|
p = strchr(p, ',') + 1;
|
||||||
|
p = strchr(p, ',') + 1;
|
||||||
|
if (!isEmpty(p))
|
||||||
|
newDataValue(NMEA_HDG, atof(p));
|
||||||
|
p = strchr(p, ',') + 1;
|
||||||
|
p = strchr(p, ',') + 1;
|
||||||
|
if (!isEmpty(p))
|
||||||
|
newDataValue(NMEA_VTW, atof(p)); // skip the other units
|
||||||
|
|
||||||
|
} else if (!strcmp(thisSentence, "VLW")) { //*****************************VLW
|
||||||
|
// from Actisense NGW-1
|
||||||
|
if (!isEmpty(p))
|
||||||
|
newDataValue(NMEA_LOG, atof(p));
|
||||||
|
p = strchr(p, ',') + 1;
|
||||||
|
p = strchr(p, ',') + 1;
|
||||||
|
if (!isEmpty(p))
|
||||||
|
newDataValue(NMEA_LOGR, atof(p)); // skip the other units
|
||||||
|
|
||||||
|
} else if (!strcmp(thisSentence, "VPW")) { //*****************************VPW
|
||||||
|
// knots, metres/s coerced to knots
|
||||||
|
nmea_float_t vmg = 100000.;
|
||||||
|
if (!isEmpty(p))
|
||||||
|
vmg = atof(p);
|
||||||
|
p = strchr(p, ',') + 1;
|
||||||
|
p = strchr(p, ',') + 1;
|
||||||
|
if (!isEmpty(p))
|
||||||
|
vmg = atof(p) * 0.3048 * 3600. / 6000.; // skip units
|
||||||
|
if (vmg < 1000.)
|
||||||
|
newDataValue(NMEA_VMG, vmg);
|
||||||
|
} else if (!strcmp(thisSentence, "VTG")) { //*****************************VTG
|
||||||
|
// from Actisense NGW-1 from SH CP150C
|
||||||
|
return false;
|
||||||
|
|
||||||
|
} else if (!strcmp(thisSentence, "VWR")) { //*****************************VWR
|
||||||
|
// from Actisense NGW-1
|
||||||
|
nmea_float_t ang = 1000.;
|
||||||
|
if (!isEmpty(p))
|
||||||
|
ang = atof(p);
|
||||||
|
p = strchr(p, ',') + 1;
|
||||||
|
char ref = ' ';
|
||||||
|
if (!isEmpty(p))
|
||||||
|
ref = *p;
|
||||||
|
p = strchr(p, ',') + 1;
|
||||||
|
if (ref == 'L')
|
||||||
|
ang *= -1;
|
||||||
|
if (ang < 1000.)
|
||||||
|
newDataValue(NMEA_AWA, ang);
|
||||||
|
nmea_float_t ws = 0.0;
|
||||||
|
char units = 'X';
|
||||||
|
if (!isEmpty(p))
|
||||||
|
ws = atof(p);
|
||||||
|
p = strchr(p, ',') + 1; // knots
|
||||||
|
if (!isEmpty(p))
|
||||||
|
units = *p;
|
||||||
|
p = strchr(p, ',') + 1;
|
||||||
|
if (!isEmpty(p))
|
||||||
|
ws = atof(p);
|
||||||
|
p = strchr(p, ',') + 1; // meters / second
|
||||||
|
if (!isEmpty(p))
|
||||||
|
units = *p;
|
||||||
|
p = strchr(p, ',') + 1; // M
|
||||||
|
if (!isEmpty(p))
|
||||||
|
ws = atof(p);
|
||||||
|
p = strchr(p, ',') + 1; // kilometers / hour can be converted back to knots
|
||||||
|
if (!isEmpty(p))
|
||||||
|
units = *p; // last before checksum
|
||||||
|
if (units == 'M') {
|
||||||
|
ws *= 3.6;
|
||||||
|
units = 'K';
|
||||||
|
} // convert m/s to km/h
|
||||||
|
if (units == 'K') {
|
||||||
|
ws /= 1.6;
|
||||||
|
units = 'M';
|
||||||
|
} // convert km/h to miles / h
|
||||||
|
if (units == 'M') {
|
||||||
|
ws *= 5280. / 6000.;
|
||||||
|
units = 'N';
|
||||||
|
} // convert miles / hr to knots
|
||||||
|
if (units == 'N')
|
||||||
|
newDataValue(NMEA_AWS, ws); // store the final result
|
||||||
|
|
||||||
|
} else if (!strcmp(thisSentence, "WCV")) { //*****************************WCV
|
||||||
|
// from SH CP150C
|
||||||
|
if (!isEmpty(p))
|
||||||
|
newDataValue(NMEA_VMGWP, atof(p)); // skip the rest
|
||||||
|
|
||||||
|
} else if (!strcmp(thisSentence, "XTE")) { //*****************************XTE
|
||||||
|
// from Actisense NGW-1 from SH CP150C
|
||||||
|
p = strchr(p, ',') + 1; // skip status 1
|
||||||
|
p = strchr(p, ',') + 1; // skip status 2
|
||||||
|
nmea_float_t xte = 100000.;
|
||||||
|
char xteDir = 'X';
|
||||||
|
if (!isEmpty(p))
|
||||||
|
xte = atof(p);
|
||||||
|
p = strchr(p, ',') + 1;
|
||||||
|
if (!isEmpty(p))
|
||||||
|
xteDir = *p;
|
||||||
|
p = strchr(p, ',') + 1;
|
||||||
|
if (xte < 10000. && xteDir != 'X') {
|
||||||
|
if (xteDir == 'L')
|
||||||
|
xte *= -1.;
|
||||||
|
newDataValue(NMEA_XTE, xte);
|
||||||
|
} // skip units
|
||||||
|
|
||||||
|
} else if (!strcmp(thisSentence, "ZDA")) { //*****************************ZDA
|
||||||
|
// from Actisense NGW-1
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
#endif // NMEA_EXTENSIONS
|
#endif // NMEA_EXTENSIONS
|
||||||
|
|
||||||
// we dont parse the remaining, yet!
|
else {
|
||||||
else
|
return false; // didn't find the required sentence definition
|
||||||
return false;
|
}
|
||||||
|
|
||||||
// Record the successful parsing of where the last data came from and when
|
// Record the successful parsing of where the last data came from and when
|
||||||
strcpy(lastSource, thisSource);
|
strcpy(lastSource, thisSource);
|
||||||
|
|
@ -229,3 +533,300 @@ bool Adafruit_GPS::parse(char *nmea) {
|
||||||
lastUpdate = millis();
|
lastUpdate = millis();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**************************************************************************/
|
||||||
|
/*!
|
||||||
|
@brief Check an NMEA string for basic format, valid source ID and valid
|
||||||
|
and valid sentence ID. Update the values of thisCheck, thisSource and
|
||||||
|
thisSentence.
|
||||||
|
@param nmea Pointer to the NMEA string
|
||||||
|
@return True if well formed, false if it has problems
|
||||||
|
*/
|
||||||
|
/**************************************************************************/
|
||||||
|
boolean Adafruit_GPS::check(char *nmea) {
|
||||||
|
thisCheck = 0; // new check
|
||||||
|
if (*nmea != '$' && *nmea != '!')
|
||||||
|
return false; // doesn't start with $ or !
|
||||||
|
else
|
||||||
|
thisCheck += NMEA_HAS_DOLLAR;
|
||||||
|
// do checksum check -- first look if we even have one -- ignore all but last
|
||||||
|
// *
|
||||||
|
char *ast = nmea; // not strchr(nmea,'*'); for first *
|
||||||
|
while (*ast)
|
||||||
|
ast++; // go to the end
|
||||||
|
while (*ast != '*' && ast > nmea)
|
||||||
|
ast--; // then back to * if it's there
|
||||||
|
if (*ast != '*')
|
||||||
|
return false; // there is no asterisk
|
||||||
|
else {
|
||||||
|
uint16_t sum = parseHex(*(ast + 1)) * 16; // extract checksum
|
||||||
|
sum += parseHex(*(ast + 2));
|
||||||
|
char *p = nmea; // check checksum
|
||||||
|
for (char *p1 = p + 1; p1 < ast; p1++)
|
||||||
|
sum ^= *p1;
|
||||||
|
if (sum != 0)
|
||||||
|
return false; // bad checksum :(
|
||||||
|
else
|
||||||
|
thisCheck += NMEA_HAS_CHECKSUM;
|
||||||
|
}
|
||||||
|
// extract source of variable length
|
||||||
|
char *p = nmea + 1;
|
||||||
|
const char *src = tokenOnList(p, sources);
|
||||||
|
if (src) {
|
||||||
|
strcpy(thisSource, src);
|
||||||
|
thisCheck += NMEA_HAS_SOURCE;
|
||||||
|
} else
|
||||||
|
return false;
|
||||||
|
p += strlen(src);
|
||||||
|
// extract sentence id and check if parsed
|
||||||
|
const char *snc = tokenOnList(p, sentences_parsed);
|
||||||
|
if (snc) {
|
||||||
|
strcpy(thisSentence, snc);
|
||||||
|
thisCheck += NMEA_HAS_SENTENCE_P + NMEA_HAS_SENTENCE;
|
||||||
|
} else { // check if known
|
||||||
|
snc = tokenOnList(p, sentences_known);
|
||||||
|
if (snc) {
|
||||||
|
strcpy(thisSentence, snc);
|
||||||
|
thisCheck += NMEA_HAS_SENTENCE;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true; // passed all the tests
|
||||||
|
}
|
||||||
|
|
||||||
|
/**************************************************************************/
|
||||||
|
/*!
|
||||||
|
@brief Check if a token at the start of a string is on a list.
|
||||||
|
@param token Pointer to the string
|
||||||
|
@param list A list of strings, with the final entry starting "ZZ"
|
||||||
|
@return Pointer to the found token, or NULL if it fails
|
||||||
|
*/
|
||||||
|
/**************************************************************************/
|
||||||
|
const char *Adafruit_GPS::tokenOnList(char *token, const char **list) {
|
||||||
|
int i = 0; // index in the list
|
||||||
|
while (strncmp(list[i], "ZZ", 2) &&
|
||||||
|
i < 1000) { // stop at terminator and don't crash without it
|
||||||
|
// test for a match on the sentence name
|
||||||
|
if (!strncmp((const char *)list[i], (const char *)token, strlen(list[i])))
|
||||||
|
return list[i];
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
return NULL; // couldn't find a match
|
||||||
|
}
|
||||||
|
|
||||||
|
/**************************************************************************/
|
||||||
|
/*!
|
||||||
|
@brief Check if an NMEA string is valid and is on a list, perhaps to
|
||||||
|
decide if it should be passed to a particular NMEA device.
|
||||||
|
@param nmea Pointer to the NMEA string
|
||||||
|
@param list A list of strings, with the final entry "ZZ"
|
||||||
|
@return True if on the list, false if it fails check or is not on the list
|
||||||
|
*/
|
||||||
|
/**************************************************************************/
|
||||||
|
bool Adafruit_GPS::onList(char *nmea, const char **list) {
|
||||||
|
if (!check(nmea)) // sets thisSentence if valid
|
||||||
|
return false; // not a valid sentence
|
||||||
|
// stop at terminator with first two letters ZZ and don't crash without it
|
||||||
|
for (int i = 0; strncmp(list[i], "ZZ", 2) && i < 1000; i++) {
|
||||||
|
// test for a match on the sentence name
|
||||||
|
if (!strcmp((const char *)list[i], (const char *)thisSentence))
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false; // couldn't find a match
|
||||||
|
}
|
||||||
|
|
||||||
|
/**************************************************************************/
|
||||||
|
/*!
|
||||||
|
@brief Parse a part of an NMEA string for lat or lon angle and direction.
|
||||||
|
Works for either DDMM.mmmm,N (latitude) or DDDMM.mmmm,W (longitude) format.
|
||||||
|
Insensitive to number of decimal places present. Only fills the variables
|
||||||
|
if it succeeds and the variable pointer is not NULL. This allows calling
|
||||||
|
to fill only the variables of interest. Does rudimentary validation on
|
||||||
|
angle range.
|
||||||
|
|
||||||
|
Supersedes private functions parseLat(), parseLon(), parseLatDir(),
|
||||||
|
parseLonDir(), all previously called from parse().
|
||||||
|
@param pStart Pointer to the location of the token in the NMEA string
|
||||||
|
@param angle Pointer to the angle to fill with value in degrees/minutes as
|
||||||
|
received from the GPS (DDDMM.MMMM), unsigned
|
||||||
|
@param angle_fixed Pointer to the fix point version latitude in decimal
|
||||||
|
degrees * 10000000, signed
|
||||||
|
@param angleDegrees Pointer to the angle to fill with decimal degrees,
|
||||||
|
signed. As actual double on SAMD, etc. resolution is better than the
|
||||||
|
fixed point version.
|
||||||
|
@param dir Pointer to character to fill the direction N/S/E/W
|
||||||
|
@return true if successful, false if failed or no value
|
||||||
|
*/
|
||||||
|
/**************************************************************************/
|
||||||
|
bool Adafruit_GPS::parseCoord(char *pStart, nmea_float_t *angleDegrees,
|
||||||
|
nmea_float_t *angle, int32_t *angle_fixed,
|
||||||
|
char *dir) {
|
||||||
|
char *p = pStart;
|
||||||
|
if (!isEmpty(p)) {
|
||||||
|
// get the number in DDDMM.mmmm format and break into components
|
||||||
|
char degreebuff[10];
|
||||||
|
char *e = strchr(p, '.');
|
||||||
|
if (e == NULL || e - p > 6)
|
||||||
|
return false; // no decimal point in range
|
||||||
|
strncpy(degreebuff, p, e - p); // get DDDMM
|
||||||
|
long dddmm = atol(degreebuff);
|
||||||
|
long degrees = (dddmm / 100); // truncate the minutes
|
||||||
|
long minutes = dddmm - degrees * 100; // remove the degrees
|
||||||
|
p = e; // start from the decimal point
|
||||||
|
nmea_float_t decminutes = atof(e); // the fraction after the decimal point
|
||||||
|
p = strchr(p, ',') + 1; // go to the next field
|
||||||
|
|
||||||
|
// get the NSEW direction as a character
|
||||||
|
char nsew = 'X';
|
||||||
|
if (!isEmpty(p))
|
||||||
|
nsew = *p; // field is not empty
|
||||||
|
else
|
||||||
|
return false; // no direction provided
|
||||||
|
|
||||||
|
// set the various numerical formats to their values
|
||||||
|
long fixed = degrees * 10000000 + (minutes * 10000000) / 60 +
|
||||||
|
(decminutes * 10000000) / 60;
|
||||||
|
nmea_float_t ang = degrees * 100 + minutes + decminutes;
|
||||||
|
nmea_float_t deg = fixed / (nmea_float_t)10000000.;
|
||||||
|
if (nsew == 'S' ||
|
||||||
|
nsew == 'W') { // fixed and deg are signed, but DDDMM.mmmm is not
|
||||||
|
fixed = -fixed;
|
||||||
|
deg = -deg;
|
||||||
|
}
|
||||||
|
|
||||||
|
// reject directions that are not NSEW
|
||||||
|
if (nsew != 'N' && nsew != 'S' && nsew != 'E' && nsew != 'W')
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// reject angles that are out of range
|
||||||
|
if (nsew == 'N' || nsew == 'S')
|
||||||
|
if (abs(deg) > 90)
|
||||||
|
return false;
|
||||||
|
if (abs(deg) > 180)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// store in locations passed as args
|
||||||
|
if (angle != NULL)
|
||||||
|
*angle = ang;
|
||||||
|
if (angle_fixed != NULL)
|
||||||
|
*angle_fixed = fixed;
|
||||||
|
if (angleDegrees != NULL)
|
||||||
|
*angleDegrees = deg;
|
||||||
|
if (dir != NULL)
|
||||||
|
*dir = nsew;
|
||||||
|
} else
|
||||||
|
return false; // no number
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**************************************************************************/
|
||||||
|
/*!
|
||||||
|
@brief Parse a string token from pointer p to the next comma, asterisk
|
||||||
|
or end of string.
|
||||||
|
@param buff Pointer to the buffer to store the string in
|
||||||
|
@param p Pointer into a string
|
||||||
|
@param n Max permitted size of string including terminating 0
|
||||||
|
@return Pointer to the string buffer
|
||||||
|
*/
|
||||||
|
/**************************************************************************/
|
||||||
|
char *Adafruit_GPS::parseStr(char *buff, char *p, int n) {
|
||||||
|
char *e = strchr(p, ',');
|
||||||
|
int len = 0;
|
||||||
|
if (e) {
|
||||||
|
len = min(e - p, n - 1);
|
||||||
|
strncpy(buff, p, len); // copy up to the comma
|
||||||
|
buff[len] = 0;
|
||||||
|
} else {
|
||||||
|
e = strchr(p, '*');
|
||||||
|
if (e) {
|
||||||
|
len = min(e - p, n - 1);
|
||||||
|
strncpy(buff, p, len); // or up to the *
|
||||||
|
buff[e - p] = 0;
|
||||||
|
} else {
|
||||||
|
len = min((int)strlen(p), n - 1);
|
||||||
|
strncpy(buff, p, len); // or to the end or max capacity
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return buff;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**************************************************************************/
|
||||||
|
/*!
|
||||||
|
@brief Parse a part of an NMEA string for time. Independent of number
|
||||||
|
of decimal places after the '.'
|
||||||
|
@param p Pointer to the location of the token in the NMEA string
|
||||||
|
@return true if successful, false otherwise
|
||||||
|
*/
|
||||||
|
/**************************************************************************/
|
||||||
|
bool Adafruit_GPS::parseTime(char *p) {
|
||||||
|
if (!isEmpty(p)) { // get time
|
||||||
|
uint32_t time = atol(p);
|
||||||
|
hour = time / 10000;
|
||||||
|
minute = (time % 10000) / 100;
|
||||||
|
seconds = (time % 100);
|
||||||
|
p = strchr(p, '.');
|
||||||
|
milliseconds = atof(p) * 1000;
|
||||||
|
lastTime = sentTime;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**************************************************************************/
|
||||||
|
/*!
|
||||||
|
@brief Parse a part of an NMEA string for whether there is a fix
|
||||||
|
@param p Pointer to the location of the token in the NMEA string
|
||||||
|
@return True if we parsed it, false if it has invalid data
|
||||||
|
*/
|
||||||
|
/**************************************************************************/
|
||||||
|
boolean Adafruit_GPS::parseFix(char *p) {
|
||||||
|
if (!isEmpty(p)) {
|
||||||
|
if (p[0] == 'A') {
|
||||||
|
fix = true;
|
||||||
|
lastFix = sentTime;
|
||||||
|
} else if (p[0] == 'V')
|
||||||
|
fix = false;
|
||||||
|
else
|
||||||
|
return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**************************************************************************/
|
||||||
|
/*!
|
||||||
|
@brief Is the field empty, or should we try conversion? Won't work
|
||||||
|
for a text field that starts with an asterisk or a comma, but that
|
||||||
|
probably violates the NMEA-183 standard.
|
||||||
|
@param pStart Pointer to the location of the token in the NMEA string
|
||||||
|
@return true if empty field, false if something there
|
||||||
|
*/
|
||||||
|
/**************************************************************************/
|
||||||
|
bool Adafruit_GPS::isEmpty(char *pStart) {
|
||||||
|
if (',' != *pStart && '*' != *pStart && pStart != NULL)
|
||||||
|
return false;
|
||||||
|
else
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**************************************************************************/
|
||||||
|
/*!
|
||||||
|
@brief Parse a hex character and return the appropriate decimal value
|
||||||
|
@param c Hex character, e.g. '0' or 'B'
|
||||||
|
@return Integer value of the hex character. Returns 0 if c is not a proper
|
||||||
|
character
|
||||||
|
*/
|
||||||
|
/**************************************************************************/
|
||||||
|
// read a Hex value and return the decimal equivalent
|
||||||
|
uint8_t Adafruit_GPS::parseHex(char c) {
|
||||||
|
if (c < '0')
|
||||||
|
return 0;
|
||||||
|
if (c <= '9')
|
||||||
|
return c - '0';
|
||||||
|
if (c < 'A')
|
||||||
|
return 0;
|
||||||
|
if (c <= 'F')
|
||||||
|
return (c - 'A') + 10;
|
||||||
|
// if (c > 'F')
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue