From 6ea92a021d6332adc41874e99a36f5bedfaa87cf Mon Sep 17 00:00:00 2001 From: Rick Sellens Date: Wed, 29 Jan 2020 09:31:41 -0500 Subject: [PATCH 01/13] Create keywords.txt --- keywords.txt | 216 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 216 insertions(+) create mode 100644 keywords.txt diff --git a/keywords.txt b/keywords.txt new file mode 100644 index 0000000..0bc963b --- /dev/null +++ b/keywords.txt @@ -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 From bc038a14cf898c9879531f213795bc6f22cc7240 Mon Sep 17 00:00:00 2001 From: Rick Sellens Date: Wed, 29 Jan 2020 09:33:26 -0500 Subject: [PATCH 02/13] Example updates --- .../GPS_HardwareSerial_Timing.ino | 7 +- examples/NMEA_EXTENSIONS/NMEA_EXTENSIONS.ino | 143 ++++++++++++++++++ 2 files changed, 147 insertions(+), 3 deletions(-) create mode 100644 examples/NMEA_EXTENSIONS/NMEA_EXTENSIONS.ino diff --git a/examples/GPS_HardwareSerial_Timing/GPS_HardwareSerial_Timing.ino b/examples/GPS_HardwareSerial_Timing/GPS_HardwareSerial_Timing.ino index 41ab240..de57086 100644 --- a/examples/GPS_HardwareSerial_Timing/GPS_HardwareSerial_Timing.ino +++ b/examples/GPS_HardwareSerial_Timing/GPS_HardwareSerial_Timing.ino @@ -34,9 +34,10 @@ Adafruit_GPS GPS(&GPSSerial); #ifdef NMEA_EXTENSIONS // Create another GPS object to hold the state of the boat, with no -// communications, so don't call Boat.begin() in setup. We will build some fake -// sentences from the Boat data to feed to GPS for testing. -Adafruit_GPS Boat(&GPSSerial); +// communications, so you don't need to call Boat.begin() in setup. +// We will build some fake sentences from the Boat data to feed to +// GPS for testing. +Adafruit_GPS Boat; #endif // Set GPSECHO to 'false' to turn off echoing the GPS data to the Serial console diff --git a/examples/NMEA_EXTENSIONS/NMEA_EXTENSIONS.ino b/examples/NMEA_EXTENSIONS/NMEA_EXTENSIONS.ino new file mode 100644 index 0000000..047bc11 --- /dev/null +++ b/examples/NMEA_EXTENSIONS/NMEA_EXTENSIONS.ino @@ -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 From 6f41d311cd77066f79247614a8dd700faeaadac5 Mon Sep 17 00:00:00 2001 From: Rick Sellens Date: Wed, 29 Jan 2020 09:56:21 -0500 Subject: [PATCH 03/13] Add no comms constructor, destructor, data values --- src/Adafruit_GPS.cpp | 25 +- src/Adafruit_GPS.h | 51 +++- src/NMEA_data.cpp | 563 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 632 insertions(+), 7 deletions(-) create mode 100644 src/NMEA_data.cpp diff --git a/src/Adafruit_GPS.cpp b/src/Adafruit_GPS.cpp index baec1e7..9d9c3f0 100644 --- a/src/Adafruit_GPS.cpp +++ b/src/Adafruit_GPS.cpp @@ -434,7 +434,7 @@ char Adafruit_GPS::read(void) { uint32_t tStart = millis(); // as close as we can get to time char was sent char c = 0; - if (paused) + if (paused || noComms) return c; #if (defined(__AVR__) || defined(ESP8266)) && defined(USE_SW_SERIAL) @@ -578,6 +578,16 @@ Adafruit_GPS::Adafruit_GPS(SPIClass *theSPI, int8_t cspin) { gpsSPI_cs = cspin; } +/**************************************************************************/ +/*! + @brief Constructor when there are no communications attached +*/ +/**************************************************************************/ +Adafruit_GPS::Adafruit_GPS() { + common_init(); // Set everything to common state, then... + noComms = true; +} + /**************************************************************************/ /*! @brief Initialization code used by all constructor types @@ -605,6 +615,19 @@ void Adafruit_GPS::common_init(void) { HDOP = VDOP = PDOP = 0.0; // nmea_float_t } +/**************************************************************************/ +/*! + @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 +} + /**************************************************************************/ /*! @brief Start the HW or SW serial port diff --git a/src/Adafruit_GPS.h b/src/Adafruit_GPS.h index 145fce5..8da85b2 100644 --- a/src/Adafruit_GPS.h +++ b/src/Adafruit_GPS.h @@ -83,7 +83,9 @@ public: Adafruit_GPS(HardwareSerial *ser); // Constructor when using HardwareSerial Adafruit_GPS(TwoWire *theWire); // Constructor when using I2C Adafruit_GPS(SPIClass *theSPI, int8_t cspin); // Constructor when using SPI - + Adafruit_GPS(); // Constructor for no communications, just data storage + virtual ~Adafruit_GPS(); + char *lastNMEA(void); bool newNMEAreceived(); void common_init(void); @@ -100,7 +102,6 @@ public: bool check(char *nmea); bool parse(char *); - void addChecksum(char *buff); nmea_float_t secondsSinceFix(); nmea_float_t secondsSinceTime(); nmea_float_t secondsSinceDate(); @@ -109,6 +110,32 @@ public: bool wakeup(void); bool standby(void); + // NMEA_build.cpp +#ifdef NMEA_EXTENSIONS + char *build(char *nmea, const char *thisSource, const char *thisSentence, + char ref = 'R'); +#endif + void addChecksum(char *buff); + + // NMEA_data.cpp + void newDataValue(nmea_index_t tag, nmea_float_t v); +#ifdef NMEA_EXTENSIONS + nmea_float_t get(nmea_index_t idx); + nmea_float_t getSmoothed(nmea_index_t idx); + void initDataValue(nmea_index_t idx, char *label = NULL, char *fmt = NULL, + char *unit = NULL, unsigned long response = 0, + nmea_value_type_t type = NMEA_SIMPLE_FLOAT); + nmea_history_t *initHistory(nmea_index_t idx, nmea_float_t scale = 10.0, + nmea_float_t offset = 0.0, + unsigned historyInterval = 20, + unsigned historyN = 192); + void removeHistory(nmea_index_t idx); + void showDataValue(nmea_index_t idx, int n = 7); + bool isCompoundAngle(nmea_index_t idx); +#endif + nmea_float_t boatAngle(nmea_float_t s, nmea_float_t c); + nmea_float_t compassAngle(nmea_float_t s, nmea_float_t c); + int thisCheck = 0; ///< the results of the check on the current sentence char thisSource[NMEA_MAX_SOURCE_ID] = { 0}; ///< the first two letters of the current sentence, e.g. WI, GP @@ -180,11 +207,20 @@ public: uint8_t LOCUS_percent; ///< Log life used percentage #ifdef NMEA_EXTENSIONS - // NMEA additional public functions - char *build(char *nmea, const char *thisSource, const char *thisSentence, - char ref = 'R'); - // NMEA additional public variables + nmea_datavalue_t + val[NMEA_MAX_INDEX]; ///< an array of data value structs, val[0] = most + ///< recent HDOP so that ockam indexing works + nmea_float_t depthToKeel = + 2.4; ///< depth from surface to bottom of keel in metres + nmea_float_t depthToTransducer = + 0.0; ///< depth of transducer below the surface in metres + + char toID[NMEA_MAX_WP_ID] = { + 0}; ///< id of waypoint going to on this segment of the route + char fromID[NMEA_MAX_WP_ID] = { + 0}; ///< id of waypoint coming from on this segment of the route + char txtTXT[63] = {0}; ///< text content from most recent TXT sentence int txtTot = 0; ///< total TXT sentences in group int txtID = 0; ///< id of the text message @@ -200,6 +236,8 @@ private: bool parseLatDir(char *); void parseLon(char *); bool parseLonDir(char *); + // NMEA_data.cpp + void data_init(); bool parseFix(char *); // used by check() for validity tests, room for future expansion const char *sources[5] = {"II", "WI", "GP", "GN", @@ -226,6 +264,7 @@ private: #if (defined(__AVR__) || defined(ESP8266)) && defined(USE_SW_SERIAL) SoftwareSerial *gpsSwSerial; #endif + bool noComms = false; HardwareSerial *gpsHwSerial; TwoWire *gpsI2C; SPIClass *gpsSPI; diff --git a/src/NMEA_data.cpp b/src/NMEA_data.cpp new file mode 100644 index 0000000..c5d0c15 --- /dev/null +++ b/src/NMEA_data.cpp @@ -0,0 +1,563 @@ +/**************************************************************************/ +/*! + @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 From 2a5b5bdd1c7d974ce80cc30de02214b2bb46b530 Mon Sep 17 00:00:00 2001 From: Rick Sellens Date: Wed, 29 Jan 2020 09:57:49 -0500 Subject: [PATCH 04/13] Update source and sentence lists --- src/Adafruit_GPS.h | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/src/Adafruit_GPS.h b/src/Adafruit_GPS.h index 8da85b2..fc980fc 100644 --- a/src/Adafruit_GPS.h +++ b/src/Adafruit_GPS.h @@ -240,12 +240,26 @@ private: void data_init(); bool parseFix(char *); // used by check() for validity tests, room for future expansion - const char *sources[5] = {"II", "WI", "GP", "GN", - "ZZZ"}; ///< valid source ids - const char *sentences_parsed[5] = {"GGA", "GLL", "GSA", "RMC", - "ZZZ"}; ///< parseable sentence ids - const char *sentences_known[1] = { - "ZZZ"}; ///< known, but not parseable sentence ids + const char *sources[6] = {"II", "WI", "GP", "GN", + "P", "ZZZ"}; ///< valid source ids +#ifdef NMEA_EXTENSIONS + const char + *sentences_parsed[20] = + { + "GGA", "GLL", "GSA", "RMC", "DBT", "HDM", "HDT", + "MDA", "MTW", "MWV", "RMB", "TXT", "VHW", "VLW", + "VPW", "VWR", "WCV", "XTE", "ZZZ"}; ///< parseable sentence ids + const char *sentences_known[15] = { + "APB", "DPT", "GSV", "HDG", "MWD", "ROT", + "RPM", "RSA", "VDR", "VTG", "ZDA", "ZZZ"}; ///< known, but not parseable +#else // make the lists short to save memory + const char + *sentences_parsed[5] = + { + "GGA", "GLL", "GSA", "RMC", "ZZZ"}; ///< parseable sentence ids + const char *sentences_known[4] = { + "DBT", "HDM", "HDT", "ZZZ"}; ///< known, but not parseable +#endif // Make all of these times far in the past by setting them near the middle of // the millis() range. Timing assumes that sentences are parsed promptly. From 0b0996bbb09d04808a82cb77c9e67b2d64e481bd Mon Sep 17 00:00:00 2001 From: Rick Sellens Date: Wed, 29 Jan 2020 09:59:47 -0500 Subject: [PATCH 05/13] Add NMEA_EXTRAS for command line compile control --- src/Adafruit_GPS.h | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/Adafruit_GPS.h b/src/Adafruit_GPS.h index fc980fc..75cf7b3 100644 --- a/src/Adafruit_GPS.h +++ b/src/Adafruit_GPS.h @@ -31,8 +31,14 @@ Comment out the definition of NMEA_EXTENSIONS to make the library use as little memory as possible for GPS functionality only. The ARDUINO_ARCH_AVR test should leave it out of any compilations for the UNO and similar. */ -#ifndef ARDUINO_ARCH_AVR -#define NMEA_EXTENSIONS ///< if defined will include more NMEA sentences +#ifndef NMEA_EXTRAS // inject on the compile command line to force extensions + #ifndef ARDUINO_ARCH_AVR + #define NMEA_EXTENSIONS ///< if defined will include more NMEA sentences + #endif +#else + #if(NMEA_EXTRAS > 0) + #define NMEA_EXTENSIONS ///< if defined will include more NMEA sentences + #endif #endif #define USE_SW_SERIAL ///< comment this out if you don't want to include From 4b6e1817702f276cec8e53d9b7f08e34f7ae2bd0 Mon Sep 17 00:00:00 2001 From: Rick Sellens Date: Wed, 29 Jan 2020 10:03:59 -0500 Subject: [PATCH 06/13] New version of build() with more sentences move addChecksum() to NMEA_build.cpp --- src/Adafruit_GPS.cpp | 21 --- src/NMEA_build.cpp | 437 ++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 416 insertions(+), 42 deletions(-) diff --git a/src/Adafruit_GPS.cpp b/src/Adafruit_GPS.cpp index 9d9c3f0..937bedb 100644 --- a/src/Adafruit_GPS.cpp +++ b/src/Adafruit_GPS.cpp @@ -159,27 +159,6 @@ bool Adafruit_GPS::isEmpty(char *pStart) { 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 diff --git a/src/NMEA_build.cpp b/src/NMEA_build.cpp index fc2a4a1..3cfc1f7 100644 --- a/src/NMEA_build.cpp +++ b/src/NMEA_build.cpp @@ -44,11 +44,17 @@ extension to building proprietary sentences like $PMTK220,100*2F. 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 NMEA-183, so send your output with a print, not a println. + The resulting sentence may be corrupted if the input data is corrupt. + In particular, the sentence will be truncated if any of the character + data is 0, e.g. if lat is not set to 'N' or 'S'. + Some of the data in these test sentences may be arbitrary, e.g. for the TXT sentence which has a more complicated protocol for multiple lines sent as a message set. Also, the data in the class variables are presumed @@ -66,7 +72,7 @@ /**************************************************************************/ char *Adafruit_GPS::build(char *nmea, const char *thisSource, const char *thisSentence, char ref) { - sprintf(nmea, "%6.2f", 123.45); // fail if sprintf() doesn't handle floats + sprintf(nmea, "%6.2f", (double)123.45); // fail if sprintf() doesn't handle floats if (strcmp(nmea, "123.45")) return NULL; *nmea = '$'; @@ -82,8 +88,7 @@ char *Adafruit_GPS::build(char *nmea, const char *thisSource, // pruning excess code easier. Otherwise, keep them alphabetical for ease of // reading. - if (!strcmp(thisSentence, - "GGA")) { //********************************************GGA + if (!strcmp(thisSentence, "GGA")) { //************************************GGA // GGA Global Positioning System Fix Data. Time, Position and fix related // data for a GPS receiver // 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 @@ -107,12 +112,11 @@ char *Adafruit_GPS::build(char *nmea, const char *thisSource, // 14) Differential reference station ID, 0000-1023 // 15) Checksum sprintf(p, "%09.2f,%09.4f,%c,%010.4f,%c,%d,%02d,%f,%f,M,%f,M,,", - hour * 10000L + minute * 100L + seconds + milliseconds / 1000., - (double)latitude, lat, (double)longitude, lon, fixquality, - satellites, (double)HDOP, (double)altitude, (double)geoidheight); + (double)hour * 10000L + minute * 100L + seconds + milliseconds / 1000., + (double)latitude, lat, (double)longitude, lon, fixquality, satellites, + (double)HDOP, (double)altitude, (double)geoidheight); - } else if (!strcmp(thisSentence, - "GLL")) { //********************************************GLL + } else if (!strcmp(thisSentence, "GLL")) { //*****************************GLL // GLL Geographic Position – Latitude/Longitude // 1 2 3 4 5 6 7 // | | | | | | | @@ -125,11 +129,10 @@ char *Adafruit_GPS::build(char *nmea, const char *thisSource, // 6) Status A - Data Valid, V - Data Invalid // 7) Checksum sprintf(p, "%09.4f,%c,%010.4f,%c,%09.2f,A", (double)latitude, lat, - (double)longitude, lon, - hour * 10000L + minute * 100L + seconds + milliseconds / 1000.); + (double)longitude, lon, (double)hour * 10000L + minute * 100L + + seconds + milliseconds / 1000.); - } else if (!strcmp(thisSentence, - "GSA")) { //******************************************** + } else if (!strcmp(thisSentence, "GSA")) { //*****************************GSA // GSA GPS DOP and active satellites // 1 2 3 14 15 16 17 18 // | | | | | | | | @@ -146,8 +149,7 @@ char *Adafruit_GPS::build(char *nmea, const char *thisSource, // 18) Checksum return NULL; - } else if (!strcmp(thisSentence, - "RMC")) { //********************************************RMC + } else if (!strcmp(thisSentence, "RMC")) { //*****************************RMC // RMC Recommended Minimum Navigation Information // 12 // 1 2 3 4 5 6 7 8 9 10 11 | @@ -166,13 +168,255 @@ char *Adafruit_GPS::build(char *nmea, const char *thisSource, // 11) E or W // 12) Checksum sprintf(p, "%09.2f,A,%09.4f,%c,%010.4f,%c,%f,%f,%06d,%f,%c", - hour * 10000L + minute * 100L + seconds + milliseconds / 1000., - (double)latitude, lat, (double)longitude, lon, (double)speed, - (double)angle, day * 10000 + month * 100 + year, - (double)magvariation, mag); + (double)hour * 10000L + minute * 100L + seconds + milliseconds / 1000., + (double)latitude, lat, (double)longitude, lon, (double)speed, + (double)angle, day * 10000 + month * 100 + year, (double)magvariation, mag); - } else if (!strcmp(thisSentence, - "TXT")) { //********************************************TXT + } else if (!strcmp(thisSentence, "APB")) { //*****************************APB + // APB Autopilot Sentence "B" + // 13 15 + // 1 2 3 4 5 6 7 8 9 10 11 12 | 14 | + // | | | | | | | | | | | | | | | + //$--APB,A,A,x.x,a,N,A,A,x.x,a,c--c,x.x,a,x.x,a*hh + // 1) Status + // V = LORAN-C Blink or SNR warning + // A = general warning flag or other navigation systems when a reliable + // fix is not available + // 2) Status + // V = Loran-C Cycle Lock warning flag + // A = OK or not used + // 3) Cross Track Error Magnitude + // 4) Direction to steer, L or R + // 5) Cross Track Units, N = Nautical Miles + // 6) Status + // A = Arrival Circle Entered + // 7) Status + // A = Perpendicular passed at waypoint + // 8) Bearing origin to destination + // 9) M = Magnetic, T = True + // 10) Destination Waypoint ID + // 11) Bearing, present position to Destination + // 12) M = Magnetic, T = True + // 13) Heading to steer to destination waypoint + // 14) M = Magnetic, T = True + // 15) Checksum + return NULL; + + } else if (!strcmp(thisSentence, "DBK")) { //*****************************DBT + // DBK Depth Below Keel + // 1 2 3 4 5 6 7 + // | | | | | | | + //$--DBK,x.x,f,x.x,M,x.x,F*hh + // 1) Depth, feet + // 2) f = feet + // 3) Depth, meters + // 4) M = meters + // 5) Depth, Fathoms + // 6) F = Fathoms + // 7) Checksum + return NULL; + + } else if (!strcmp(thisSentence, "DBS")) { //*****************************DBT + // DBS Depth Below Surface + // 1 2 3 4 5 6 7 + // | | | | | | | + //$--DBS,x.x,f,x.x,M,x.x,F*hh + // 1) Depth, feet + // 2) f = feet + // 3) Depth, meters + // 4) M = meters + // 5) Depth, Fathoms + // 6) F = Fathoms + // 7) Checksum + return NULL; + + } else if (!strcmp(thisSentence, "DBT")) { //*****************************DBT + // DBT Depth Below Transducer + // 1 2 3 4 5 6 7 + // | | | | | | | + //$--DBT,x.x,f,x.x,M,x.x,F*hh + // 1) Depth, feet + // 2) f = feet + // 3) Depth, meters + // 4) M = meters + // 5) Depth, Fathoms + // 6) F = Fathoms + // 7) Checksum + double d = val[NMEA_DEPTH].latest - depthToTransducer; + sprintf(p, "%f,f,%f,M,,,", d / 0.3048, d); + + } else if (!strcmp(thisSentence, "DPT")) { //*****************************DPT + // DPT Heading – Deviation & Variation + // 1 2 3 + // | | | + //$--DPT,x.x,x.x*hh + // 1) Depth, meters + // 2) Offset from transducer; + // positive means distance from transducer to water line, + // negative means distance from transducer to keel + // 3) Checksum + return NULL; + + } else if (!strcmp(thisSentence, "GSV")) { //*****************************GSV + // GSV Satellites in view + // 1 2 3 4 5 6 7 n + // | | | | | | | | + //$--GSV,x,x,x,x,x,x,x,...*hh + // 1) total number of messages + // 2) message number + // 3) satellites in view + // 4) satellite number + // 5) elevation in degrees + // 6) azimuth in degrees to true + // 7) SNR in dB + // more satellite infos like 4)-7) + // n) Checksum + return NULL; + + } else if (!strcmp(thisSentence, "HDG")) { //*****************************HDG + // HDG Heading – Deviation & Variation + // 1 2 3 4 5 6 + // | | | | | | + //$--HDG,x.x,x.x,a,x.x,a*hh + // 1) Magnetic Sensor heading in degrees + // 2) Magnetic Deviation, degrees + // 3) Magnetic Deviation direction, E = Easterly, W = Westerly + // 4) Magnetic Variation degrees + // 5) Magnetic Variation direction, E = Easterly, W = Westerly + // 6) Checksum + return NULL; + + } else if (!strcmp(thisSentence, "HDM")) { //*****************************HDM + // HDM Heading – Magnetic + // 1 2 3 + // | | | + //$--HDM,x.x,M*hh + // 1) Heading Degrees, magnetic + // 2) M = magnetic + // 3) Checksum + sprintf(p, "%f,M", (double)val[NMEA_HDG].latest); + + } else if (!strcmp(thisSentence, "HDT")) { //*****************************HDT + // HDT Heading – True + // 1 2 3 + // | | | + //$--HDT,x.x,T*hh + // 1) Heading Degrees, true + // 2) T = True + // 3) Checksum + // starts with $II for integrated instrumentation + sprintf(p, "%f,T", (double)val[NMEA_HDT].latest); + + } else if (!strcmp(thisSentence, "MDA")) { //*****************************MDA + // MDA Meteorological Composite + // 1 2 3 4 5 6 7 8 9 10 11 12 + // | | | | | | | | | | | | + //$__MDA,x.x,I,x.x,B,x.x,C,x.x,C,x.x, ,x.x,C,,T,,M,,N,,M*hh + //$IIMDA,,I,,B,,C,21.8,C,,,,C,,T,,M,,N,,M*0F // sent by RayMarine i70s + // Speed/Depth/Wind + // 1) Barometric Pressure + // 2) inches of Hg + // 3) Barometric Pressure + // 4) bar + // 5) Atmospheric Temperature + // 6) C or F + // 7) Water Temperature + // 8) C or F + // 9) Relative Humidity + // 10) + // 11) Dew Point + // 12) C or F + return NULL; + + } else if (!strcmp(thisSentence, "MTW")) { //*****************************MTW + // MTW Water Temperature + // 1 2 3 + // | | | + //$IIMTW,x.x,C*hh + //$IIMTW,21.8,C*18 // sent by RayMarine i70s Speed/Depth/Wind + // 1) Degrees + // 2) Unit of Measurement, Celcius + // 3) Checksum + return NULL; + + } else if (!strcmp(thisSentence, "MWD")) { //*****************************MWD + // MWD Wind Direction & Speed + // Format unknown + return NULL; + + } else if (!strcmp(thisSentence, "MWV")) { //*****************************MWV + // MWV Wind Speed and Angle assuming values for True + // 1 2 3 4 5 6 + // | | | | | | + //$IIMWV,x.x,a,x.x,a,a*hh + //$WIMWV,276.94,R,0,N,A*03 // sent by RayMarine i70s Speed/Depth/Wind + // 1) Wind Angle, 0 to 360 degrees + // 2) Reference, R = Relative, T = True + // 3) Wind Speed + // 4) Wind Speed Units, K/M/N kilometers/miles/knots + // 5) Status, A = Data Valid + // 6) Checksum + if (ref == 'R') + sprintf(p, "%f,%c,%f,N,A", (double)val[NMEA_AWA].latest, ref, + (double)val[NMEA_AWS].latest); + else + sprintf(p, "%f,%c,%f,N,A", (double)val[NMEA_TWA].latest, 'T', + (double)val[NMEA_TWS].latest); + + } else if (!strcmp(thisSentence, "RMB")) { //*****************************RMB + // RMB Recommended Minimum Navigation Information + // 1 2 3 4 5 6 7 8 9 10 11 12 13 14 + // | | | | | | | | | | | | | | + //$--RMB,A,x.x,a,c--c,c--c,llll.ll,a,yyyyy.yy,a,x.x,x.x,x.x,A*hh + // 1) Status, V = Navigation receiver warning + // 2) Cross Track error - nautical miles + // 3) Direction to Steer, Left or Right + // 4) TO Waypoint ID + // 5) FROM Waypoint ID + // 6) Destination Waypoint Latitude 7) N or S + // 8) Destination Waypoint Longitude 9) E or W + // 10) Range to destination in nautical miles + // 11) Bearing to destination in degrees True + // 12) Destination closing velocity in knots + // 13) Arrival Status, A = Arrival Circle Entered 14) Checksum + sprintf(p, ",,,,,,,,,,,%f,A", (double)val[NMEA_VMGWP].latest); + + } else if (!strcmp(thisSentence, "ROT")) { //*****************************ROT + // ROT Rate Of Turn + // 1 2 3 + // | | | + //$--ROT,x.x,A*hh + // 1) Rate Of Turn, degrees per minute, "-" means bow turns to port + // 2) Status, A means data is valid + // 3) Checksum + return NULL; + + } else if (!strcmp(thisSentence, "RPM")) { //*****************************RPM + // RPM Revolutions + // 1 2 3 4 5 6 + // | | | | | | + //$--RPM,a,x,x.x,x.x,A*hh + // 1) Source; S = Shaft, E = Engine + // 2) Engine or shaft number + // 3) Speed, Revolutions per minute + // 4) Propeller pitch, % of maximum, "-" means astern + // 5) Status, A means data is valid + // 6) Checksum + return NULL; + + } else if (!strcmp(thisSentence, "RSA")) { //*****************************RSA + // RSA Rudder Sensor Angle + // 1 2 3 4 5 + // | | | | | + //$--RSA,x.x,A,x.x,A*hh + // 1) Starboard (or single) rudder sensor, "-" means Turn To Port + // 2) Status, A means data is valid + // 3) Port rudder sensor + // 4) Status, A means data is valid + // 5) Checksum + return NULL; + + } else if (!strcmp(thisSentence, "TXT")) { //*****************************TXT // as mentioned in https://github.com/adafruit/Adafruit_GPS/issues/95 // TXT Text Transmission // 1 2 3 4 5 @@ -185,6 +429,136 @@ char *Adafruit_GPS::build(char *nmea, const char *thisSource, // 5) Checksum sprintf(p, "01,01,23,This is the text of the sample message"); + } else if (!strcmp(thisSentence, "VDR")) { //*****************************VDR + // VDR Set and Drift + // 1 2 3 4 5 6 7 + // | | | | | | | + //$--VDR,x.x,T,x.x,M,x.x,N*hh + // 1) Degress True + // 2) T = True + // 3) Degrees Magnetic + // 4) M = Magnetic + // 5) Knots (speed of current) + // 6) N = Knots + // 7) Checksum + return NULL; + + } else if (!strcmp(thisSentence, "VHW")) { //*****************************VHW + // VHW Water Speed and Heading + // 1 2 3 4 5 6 7 8 9 + // | | | | | | | | | + //$--VHW,x.x,T,x.x,M,x.x,N,x.x,K*hh + //$IIVHW,,T,,M,0,N,0,K*55 // sent by RayMarine i70s Speed/Depth/Wind + // 1) Degrees True + // 2) T = True + // 3) Degrees Magnetic + // 4) M = Magnetic + // 5) Knots (speed of vessel relative to the water) [66] + // 6) N = Knots + // 7) Kilometers (speed of vessel relative to the water) + // 8) K = Kilometres + // 9) Checksum + sprintf(p, "%f,T,%f,M,%f,N,%f,K", (double)val[NMEA_HDT].latest, + (double)val[NMEA_HDG].latest, (double)val[NMEA_VTW].latest, + (double)val[NMEA_VTW].latest * 1.829); + + } else if (!strcmp(thisSentence, "VLW")) { //*****************************VLW + // VLW Distance Traveled through Water + // 1 2 3 4 5 + // | | | | | + //$--VLW,x.x,N,x.x,N*hh + //$IIVLW,0,N,0,N,,N,,N*4D // sent by RayMarine i70s Speed/Depth/Wind + // not sure what the last two are? + // 1) Total cumulative distance + // 2) N = Nautical Miles + // 3) Distance since Reset + // 4) N = Nautical Miles + // 5) Checksum + return NULL; + + } else if (!strcmp(thisSentence, "VPW")) { //*****************************VPW + // not supported by iNavX + // VPW Speed – Measured Parallel to Wind + // 1 2 3 4 5 + // | | | | | + //$--VPW,x.x,N,x.x,M*hh + // 1) Speed, "-" means downwind + // 2) N = Knots + // 3) Speed, "-" means downwind + // 4) M = Meters per second + // 5) Checksum + sprintf(p, "%f,N,,", (double)val[NMEA_VMG].latest); + + } else if (!strcmp(thisSentence, "VTG")) { //*****************************VTG + // VTG Track Made Good and Ground Speed + // 1 2 3 4 5 6 7 8 9 + // | | | | | | | | | + //$--VTG,x.x,T,x.x,M,x.x,N,x.x,K*hh + // 1) Track Degrees 2) T = True + // 3) Track Degrees 4) M = Magnetic + // 5) Speed Knots 6) N = Knots + // 7) Speed Kilometers Per Hour 8) K = Kilometres Per Hour + // 9) Checksum + return NULL; + + } else if (!strcmp(thisSentence, "VWR")) { //*****************************VWR + // VWR Relative Wind Speed and Angle + // 1 2 3 4 5 6 7 8 9 + // | | | | | | | | | + //$--VWR,x.x,a,x.x,N,x.x,M,x.x,K*hh + //$WIVWR,83.1,L,0,N,0,M,0,K*6D // sent by RayMarine i70s + // Speed/Depth/Wind + // 1) Wind direction magnitude in degrees + // 2) Wind direction Left/Right of bow + // 3) Speed + // 4) N = Knots + // 5) Speed + // 6) M = Meters Per Second + // 7) Speed + // 8) K = Kilometers Per Hour + // 9) Checksum + return NULL; + + } else if (!strcmp(thisSentence, "WCV")) { //*****************************WCV + // WCV Waypoint Closure Velocity + // 1 2 3 4 + // | | | | + //$--WCV,x.x,N,c--c*hh + // 1) Velocity 2) N = knots 3) Waypoint ID 4) Checksum + sprintf(p, "%f,N,home", (double)val[NMEA_VMG].latest); + + } else if (!strcmp(thisSentence, "XTE")) { //*****************************XTE + // XTE Cross-Track Error – Measured + // 1 2 3 4 5 6 + // | | | | | | + //$--XTE,A,A,x.x,a,N,*hh + // 1) Status + // V = LORAN-C blink or SNR warning + // A = general warning flag or other navigation systems when a reliable + // fix is not available + // 2) Status + // V = Loran-C cycle lock warning flag + // A = OK or not used + // 3) Cross track error magnitude + // 4) Direction to steer, L or R + // 5) Cross track units. N = Nautical Miles + // 6) Checksum + return NULL; + + } else if (!strcmp(thisSentence, "ZDA")) { //*****************************ZDA + // ZDA Time & Date – UTC, Day, Month, Year and Local Time Zone + // 1 2 3 4 5 6 7 + // | | | | | | | + //$--ZDA,hhmmss.ss,xx,xx,xxxx,xx,xx*hh + // 1) Local zone minutes description, same sign as local hours + // 2) Local zone description, 00 to +/- 13 hours + // 3) Year + // 4) Month, 01 to 12 + // 5) Day, 01 to 31 + // 6) Time (UTC) + // 7) Checksum + return NULL; + } else { return NULL; // didn't find a match for the build request } @@ -196,3 +570,24 @@ char *Adafruit_GPS::build(char *nmea, const char *thisSource, } #endif // NMEA_EXTENSIONS + +/**************************************************************************/ +/*! + @brief Add *CS where CS is the two character hex checksum for all but + the first character in the string. The checksum is the result of an + exclusive or of all the characters in the string. Also useful if you + are creating new PMTK strings for controlling a GPS module and need a + checksum added. + @param buff Pointer to the string, which must be long enough + @return none +*/ +/**************************************************************************/ +void Adafruit_GPS::addChecksum(char *buff) { + char cs = 0; + int i = 1; + while (buff[i]) { + cs ^= buff[i]; + i++; + } + sprintf(buff, "%s*%02X", buff, cs); +} From 3876f29fc12008f543d19fb5679cc2804d946037 Mon Sep 17 00:00:00 2001 From: Rick Sellens Date: Wed, 29 Jan 2020 10:18:41 -0500 Subject: [PATCH 07/13] New parse() with more sentences move other parsing functions in NMEA_parse.cpp fix parseTime to be insensitive to number of decimal places and return bool. add isEmpty() checks where needed. reorder some declarations. --- src/Adafruit_GPS.cpp | 177 --------- src/Adafruit_GPS.h | 23 +- src/NMEA_parse.cpp | 829 +++++++++++++++++++++++++++++++++++++------ 3 files changed, 729 insertions(+), 300 deletions(-) diff --git a/src/Adafruit_GPS.cpp b/src/Adafruit_GPS.cpp index 937bedb..a1e72b4 100644 --- a/src/Adafruit_GPS.cpp +++ b/src/Adafruit_GPS.cpp @@ -32,117 +32,6 @@ static bool strStartsWith(const char *str, const char *prefix); -/**************************************************************************/ -/*! - @brief Check an NMEA string for basic format, valid source ID and valid - and valid sentence ID. Update the values of thisCheck, thisSource and - thisSentence. - @param nmea Pointer to the NMEA string - @return True if well formed, false if it has problems -*/ -/**************************************************************************/ -bool Adafruit_GPS::check(char *nmea) { - thisCheck = 0; // new check - if (*nmea != '$') - return false; // doesn't start with $ - else - thisCheck += NMEA_HAS_DOLLAR; - // do checksum check -- first look if we even have one -- ignore all but last - // * - char *ast = nmea; // not strchr(nmea,'*'); for first * - while (*ast) - ast++; // go to the end - while (*ast != '*' && ast > nmea) - ast--; // then back to * if it's there - if (*ast != '*') - return false; // there is no asterisk - else { - uint16_t sum = parseHex(*(ast + 1)) * 16; // extract checksum - sum += parseHex(*(ast + 2)); - char *p = nmea; // check checksum - for (char *p1 = p + 1; p1 < ast; p1++) - sum ^= *p1; - if (sum != 0) - return false; // bad checksum :( - else - thisCheck += NMEA_HAS_CHECKSUM; - } - // extract source of variable length - char *p = nmea + 1; - const char *src = tokenOnList(p, sources); - if (src) { - strcpy(thisSource, src); - thisCheck += NMEA_HAS_SOURCE; - } else - return false; - p += strlen(src); - // extract sentence id and check if parsed - const char *snc = tokenOnList(p, sentences_parsed); - if (snc) { - strcpy(thisSentence, snc); - thisCheck += NMEA_HAS_SENTENCE_P + NMEA_HAS_SENTENCE; - } else { // check if known - snc = tokenOnList(p, sentences_known); - if (snc) { - strcpy(thisSentence, snc); - thisCheck += NMEA_HAS_SENTENCE; - return false; - } - } - return true; // passed all the tests -} - -/**************************************************************************/ -/*! - @brief Check if a token at the start of a string is on a list. - @param token Pointer to the string - @param list A list of strings, with the final entry starting "ZZ" - @return Pointer to the found token, or NULL if it fails -*/ -/**************************************************************************/ -const char *Adafruit_GPS::tokenOnList(char *token, const char **list) { - int i = 0; // index in the list - while (strncmp(list[i], "ZZ", 2) && - i < 1000) { // stop at terminator and don't crash without it - // test for a match on the sentence name - if (!strncmp((const char *)list[i], (const char *)token, strlen(list[i]))) - return list[i]; - i++; - } - return NULL; // couldn't find a match -} - -/**************************************************************************/ -/*! - @brief Parse a string token from pointer p to the next comma, asterisk - or end of string. - @param buff Pointer to the buffer to store the string in - @param p Pointer into a string - @param n Max permitted size of string including terminating 0 - @return Pointer to the string buffer -*/ -/**************************************************************************/ -char *Adafruit_GPS::parseStr(char *buff, char *p, int n) { - char *e = strchr(p, ','); - int len = 0; - if (e) { - len = min(e - p, n - 1); - strncpy(buff, p, len); // copy up to the comma - buff[len] = 0; - } else { - e = strchr(p, '*'); - if (e) { - len = min(e - p, n - 1); - strncpy(buff, p, len); // or up to the * - buff[e - p] = 0; - } else { - len = min((int)strlen(p), n - 1); - strncpy(buff, p, len); // or to the end or max capacity - } - } - return buff; -} - /**************************************************************************/ /*! @brief Is the field empty, or should we try conversion? Won't work @@ -151,32 +40,6 @@ char *Adafruit_GPS::parseStr(char *buff, char *p, int n) { @param pStart Pointer to the location of the token in the NMEA string @return true if empty field, false if something there */ -/**************************************************************************/ -bool Adafruit_GPS::isEmpty(char *pStart) { - if (',' != *pStart && '*' != *pStart && pStart != NULL) - return false; - else - return true; -} - -/**************************************************************************/ -/*! - @brief Parse a part of an NMEA string for time - @param p Pointer to the location of the token in the NMEA string -*/ -/**************************************************************************/ -void Adafruit_GPS::parseTime(char *p) { - // get time - uint32_t time = atol(p); - hour = time / 10000; - minute = (time % 10000) / 100; - seconds = (time % 100); - - p = strchr(p, '.') + 1; - milliseconds = atoi(p); - lastTime = sentTime; -} - /**************************************************************************/ /*! @brief Parse a part of an NMEA string for latitude angle @@ -275,24 +138,6 @@ bool Adafruit_GPS::parseLonDir(char *p) { return true; } -/**************************************************************************/ -/*! - @brief Parse a part of an NMEA string for whether there is a fix - @param p Pointer to the location of the token in the NMEA string - @return True if we parsed it, false if it has invalid data -*/ -/**************************************************************************/ -bool Adafruit_GPS::parseFix(char *p) { - if (p[0] == 'A') { - fix = true; - lastFix = sentTime; - } else if (p[0] == 'V') - fix = false; - else - return false; - return true; -} - /**************************************************************************/ /*! @brief Time in seconds since the last position fix was obtained. Will @@ -682,28 +527,6 @@ char *Adafruit_GPS::lastNMEA(void) { return (char *)lastline; } -/**************************************************************************/ -/*! - @brief Parse a hex character and return the appropriate decimal value - @param c Hex character, e.g. '0' or 'B' - @return Integer value of the hex character. Returns 0 if c is not a proper - character -*/ -/**************************************************************************/ -// read a Hex value and return the decimal equivalent -uint8_t Adafruit_GPS::parseHex(char c) { - if (c < '0') - return 0; - if (c <= '9') - return c - '0'; - if (c < 'A') - return 0; - if (c <= 'F') - return (c - 'A') + 10; - // if (c > 'F') - return 0; -} - /**************************************************************************/ /*! @brief Wait for a specified sentence from the device diff --git a/src/Adafruit_GPS.h b/src/Adafruit_GPS.h index 75cf7b3..601ca72 100644 --- a/src/Adafruit_GPS.h +++ b/src/Adafruit_GPS.h @@ -100,14 +100,10 @@ public: void pause(bool b); - uint8_t parseHex(char c); - char read(void); size_t write(uint8_t); size_t available(void); - bool check(char *nmea); - bool parse(char *); nmea_float_t secondsSinceFix(); nmea_float_t secondsSinceTime(); nmea_float_t secondsSinceDate(); @@ -116,6 +112,12 @@ public: bool wakeup(void); bool standby(void); + // NMEA_parse.cpp + bool parse(char *); + bool check(char *nmea); + bool onList(char *nmea, const char **list); + uint8_t parseHex(char c); + // NMEA_build.cpp #ifdef NMEA_EXTENSIONS char *build(char *nmea, const char *thisSource, const char *thisSentence, @@ -234,17 +236,22 @@ public: #endif // NMEA_EXTENSIONS private: - const char *tokenOnList(char *token, const char **list); - char *parseStr(char *buff, char *p, int n); - bool isEmpty(char *pStart); - void parseTime(char *); void parseLat(char *); bool parseLatDir(char *); void parseLon(char *); bool parseLonDir(char *); // NMEA_data.cpp void data_init(); + // NMEA_parse.cpp + const char *tokenOnList(char *token, const char **list); + bool parseCoord(char *p, nmea_float_t *angleDegrees = NULL, + nmea_float_t *angle = NULL, int32_t *angle_fixed = NULL, + char *dir = NULL); + char *parseStr(char *buff, char *p, int n); + bool parseTime(char *); bool parseFix(char *); + bool isEmpty(char *pStart); + // used by check() for validity tests, room for future expansion const char *sources[6] = {"II", "WI", "GP", "GN", "P", "ZZZ"}; ///< valid source ids diff --git a/src/NMEA_parse.cpp b/src/NMEA_parse.cpp index ed3a0bb..0b98b9d 100644 --- a/src/NMEA_parse.cpp +++ b/src/NMEA_parse.cpp @@ -32,112 +32,102 @@ /**************************************************************************/ /*! - @brief Parse a NMEA string + @brief Parse a standard NMEA string and update the relevant variables. + Sentences start with a $, then a two character source identifier, then a + three character sentence identifier that defines the format, then a comma and + more comma separated fields defined by the sentence name. There are many + sentences listed that are not yet supported, including proprietary sentences + that start with P, like the $PMTK commands to the GPS modules. See the + build() function and http://fort21.ru/download/NMEAdescription.pdf for + sentence descriptions. + + Encapsulated data sentences are supported by NMEA-183, and start with ! + instead of $. https://gpsd.gitlab.io/gpsd/AIVDM.html provides details + about encapsulated data sentences used in AIS. + + parse() permits, but does not require Carriage Return and Line Feed at the + end of sentences. The end of the sentence is recognized by the * for the + checksum. parse() will not recognize a sentence without a valid checksum. + + NMEA_EXTENSIONS must be defined in order to parse more than basic + GPS module sentences. + @param nmea Pointer to the NMEA string - @return True if we parsed it, false if it has an invalid checksum or invalid - data + @return True if successfully parsed, false if fails check or parsing */ /**************************************************************************/ bool Adafruit_GPS::parse(char *nmea) { - // do checksum check if (!check(nmea)) return false; // passed the check, so there's a valid source in thisSource and a valid // sentence in thisSentence - - // look for a few common sentences char *p = nmea; // Pointer to move through the sentence -- good parsers are // non-destructive - p = strchr(p, ',') + - 1; // Skip to the character after the next comma, then check sentence. + p = strchr(p, ',') + 1; // Skip to char after the next comma, then check. - if (!strcmp(thisSentence, "GGA")) { - // found GGA - // get time + // This may look inefficient, but an M0 will get down the list in about 1 us / + // strcmp()! Put the GPS sentences from Adafruit_GPS at the top to make + // pruning excess code easier. Otherwise, keep them alphabetical for ease of + // reading. + if (!strcmp(thisSentence, "GGA")) { //************************************GGA + // Adafruit from Actisense NGW-1 from SH CP150C parseTime(p); - - // parse out latitude + p = strchr(p, ',') + 1; // parse time with specialized function + // parse out both latitude and direction, then go to next field, or fail + if (parseCoord(p, &latitudeDegrees, &latitude, &latitude_fixed, &lat)) + newDataValue(NMEA_LAT, latitudeDegrees); p = strchr(p, ',') + 1; - parseLat(p); p = strchr(p, ',') + 1; - if (!parseLatDir(p)) - return false; - - // parse out longitude + // parse out both longitude and direction, then go to next field, or fail + if (parseCoord(p, &longitudeDegrees, &longitude, &longitude_fixed, &lon)) + newDataValue(NMEA_LON, longitudeDegrees); p = strchr(p, ',') + 1; - parseLon(p); p = strchr(p, ',') + 1; - if (!parseLonDir(p)) - return false; - - p = strchr(p, ',') + 1; - if (!isEmpty(p)) { - fixquality = atoi(p); + if (!isEmpty(p)) { // if it's a , (or a * at end of sentence) the value is + // not included + fixquality = atoi(p); // needs additional processing if (fixquality > 0) { fix = true; lastFix = sentTime; } else fix = false; } - - p = strchr(p, ',') + 1; - if (!isEmpty(p)) { + p = strchr(p, ',') + 1; // then move on to the next + // Most can just be parsed with atoi() or atof(), then move on to the next. + if (!isEmpty(p)) satellites = atoi(p); - } - p = strchr(p, ',') + 1; - if (!isEmpty(p)) { - HDOP = atof(p); - } - + if (!isEmpty(p)) + newDataValue(NMEA_HDOP, HDOP = atof(p)); p = strchr(p, ',') + 1; - if (!isEmpty(p)) { + if (!isEmpty(p)) altitude = atof(p); - } - p = strchr(p, ',') + 1; - p = strchr(p, ',') + 1; - if (!isEmpty(p)) { - geoidheight = atof(p); - } - } + p = strchr(p, ',') + 1; // skip the units + if (!isEmpty(p)) + geoidheight = atof(p); // skip the rest - else if (!strcmp(thisSentence, "RMC")) { - // found RMC - // get time + } else if (!strcmp(thisSentence, "RMC")) { //*****************************RMC + // in Adafruit from Actisense NGW-1 from SH CP150C parseTime(p); - - // fix or no fix p = strchr(p, ',') + 1; - if (!parseFix(p)) - return false; - - // parse out latitude + parseFix(p); p = strchr(p, ',') + 1; - parseLat(p); + // parse out both latitude and direction, then go to next field, or fail + if (parseCoord(p, &latitudeDegrees, &latitude, &latitude_fixed, &lat)) + newDataValue(NMEA_LAT, latitudeDegrees); p = strchr(p, ',') + 1; - if (!parseLatDir(p)) - return false; - - // parse out longitude p = strchr(p, ',') + 1; - parseLon(p); + // parse out both longitude and direction, then go to next field, or fail + if (parseCoord(p, &longitudeDegrees, &longitude, &longitude_fixed, &lon)) + newDataValue(NMEA_LON, longitudeDegrees); p = strchr(p, ',') + 1; - if (!parseLonDir(p)) - return false; - - // speed p = strchr(p, ',') + 1; - if (!isEmpty(p)) { - speed = atof(p); - } - - // angle + if (!isEmpty(p)) + newDataValue(NMEA_SOG, speed = atof(p)); p = strchr(p, ',') + 1; - if (!isEmpty(p)) { - angle = atof(p); - } - + if (!isEmpty(p)) + newDataValue(NMEA_COG, angle = atof(p)); p = strchr(p, ',') + 1; if (!isEmpty(p)) { uint32_t fulldate = atof(p); @@ -145,66 +135,264 @@ bool Adafruit_GPS::parse(char *nmea) { month = (fulldate % 10000) / 100; year = (fulldate % 100); lastDate = sentTime; - } - } + } // skip the rest - else if (!strcmp(thisSentence, "GLL")) { - // found GLL - // parse out latitude - parseLat(p); + } else if (!strcmp(thisSentence, "GLL")) { //*****************************GLL + // in Adafruit from Actisense NGW-1 from SH CP150C + // parse out both latitude and direction, then go to next field, or fail + if (parseCoord(p, &latitudeDegrees, &latitude, &latitude_fixed, &lat)) + newDataValue(NMEA_LAT, latitudeDegrees); p = strchr(p, ',') + 1; - if (!parseLatDir(p)) - return false; - - // parse out longitude p = strchr(p, ',') + 1; - parseLon(p); + // parse out both longitude and direction, then go to next field, or fail + if (parseCoord(p, &longitudeDegrees, &longitude, &longitude_fixed, &lon)) + newDataValue(NMEA_LON, longitudeDegrees); p = strchr(p, ',') + 1; - if (!parseLonDir(p)) - return false; - - // get time p = strchr(p, ',') + 1; parseTime(p); - - // fix or no fix p = strchr(p, ',') + 1; - if (!parseFix(p)) - return false; - } + parseFix(p); // skip the rest - else if (!strcmp(thisSentence, "GSA")) { - // found GSA - // parse out Auto selection, but ignore them - // parse out 3d fixquality - p = strchr(p, ',') + 1; - if (!isEmpty(p)) { + } else if (!strcmp(thisSentence, "GSA")) { //*****************************GSA + // in Adafruit from Actisense NGW-1 + p = strchr(p, ',') + 1; // skip selection mode + if (!isEmpty(p)) fixquality_3d = atoi(p); - } + p = strchr(p, ',') + 1; // skip 12 Satellite PDNs without interpreting them for (int i = 0; i < 12; i++) p = strchr(p, ',') + 1; - - // parse out PDOP - p = strchr(p, ',') + 1; - if (!isEmpty(p)) { + if (!isEmpty(p)) PDOP = atof(p); - } + p = strchr(p, ',') + 1; // parse out HDOP, we also parse this from the GGA sentence. Chipset should // report the same for both + if (!isEmpty(p)) + newDataValue(NMEA_HDOP, HDOP = atof(p)); p = strchr(p, ',') + 1; - if (!isEmpty(p)) { - HDOP = atof(p); - } - // parse out VDOP - p = strchr(p, ',') + 1; - if (!isEmpty(p)) { - VDOP = atof(p); - } - } + if (!isEmpty(p)) + VDOP = atof(p); // last before checksum + } #ifdef NMEA_EXTENSIONS // Sentences not required for basic GPS functionality - else if (!strcmp(thisSentence, "TXT")) { //*******************************TXT + else if (!strcmp(thisSentence, "APB")) { //*******************************APB + // from Actisense NGW-1 from SH CP150C + return false; + + } else if (!strcmp(thisSentence, "DBT")) { //*****************************DBT + // from Actisense NGW-1 + // feet, metres, fathoms below transducer coerced to water depth from + // surface in metres + if (!isEmpty(p)) + newDataValue(NMEA_DEPTH, atof(p) * 0.3048 + depthToTransducer); + p = strchr(p, ',') + 1; + p = strchr(p, ',') + 1; + if (!isEmpty(p)) + newDataValue(NMEA_DEPTH, atof(p) + depthToTransducer); + p = strchr(p, ',') + 1; + p = strchr(p, ',') + 1; + if (!isEmpty(p)) + newDataValue(NMEA_DEPTH, atof(p) * 6 * 0.3048 + depthToTransducer); + + } else if (!strcmp(thisSentence, "DPT")) { //*****************************DPT + // from Actisense NGW-1 + return false; + + } else if (!strcmp(thisSentence, "GSV")) { //*****************************GSV + // from Actisense NGW-1 + return false; + + } else if (!strcmp(thisSentence, "HDG")) { //*****************************HDG + // from Actisense NGW-1 from SH CP150C + return false; + + } else if (!strcmp(thisSentence, "HDM")) { //*****************************HDM + if (!isEmpty(p)) + newDataValue(NMEA_HDG, atof(p)); // skip the rest + + } else if (!strcmp(thisSentence, "HDT")) { //*****************************HDT + if (!isEmpty(p)) + newDataValue(NMEA_HDT, atof(p)); // skip the rest + + } else if (!strcmp(thisSentence, "MDA")) { //*****************************MDA + // from Actisense NGW-1 + if (!isEmpty(p)) + newDataValue(NMEA_BAROMETER, atof(p) * 3386.39); + p = strchr(p, ',') + 1; + p = strchr(p, ',') + 1; + if (!isEmpty(p)) + newDataValue(NMEA_BAROMETER, atof(p) * 100000); + p = strchr(p, ',') + 1; + p = strchr(p, ',') + 1; + nmea_float_t T = 100000.; + char u = 'C'; + if (!isEmpty(p)) + T = atof(p); + p = strchr(p, ',') + 1; + if (!isEmpty(p)) + u = *p; + p = strchr(p, ',') + 1; + if (u != 'C') { + T = (T - 32) / 1.8; + u = 'C'; + } // coerce to C + if (T < 1000) + newDataValue(NMEA_TEMPERATURE_AIR, T); + T = 100000.; + u = 'C'; + if (!isEmpty(p)) + T = atof(p); + p = strchr(p, ',') + 1; + if (!isEmpty(p)) + u = *p; + p = strchr(p, ',') + 1; + if (u != 'C') { + T = (T - 32) / 1.8; + u = 'C'; + } + if (T < 1000) + newDataValue(NMEA_TEMPERATURE_WATER, T); + if (!isEmpty(p)) + newDataValue(NMEA_HUMIDITY, atof(p)); // skip the rest + + } else if (!strcmp(thisSentence, "MTW")) { //*****************************MTW + nmea_float_t T = 100000.; + char u = 'C'; + if (!isEmpty(p)) + T = atof(p); + p = strchr(p, ',') + 1; + if (!isEmpty(p)) + u = *p; // last before checksum + if (u != 'C') { + T = (T - 32) / 1.8; + u = 'C'; + } + if (T < 1000) + newDataValue(NMEA_TEMPERATURE_WATER, T); + + } else if (!strcmp(thisSentence, "MWD")) { //*****************************MWD + // from Actisense NGW-1 + return false; + + } else if (!strcmp(thisSentence, "MWV")) { //*****************************MWV + // from Actisense NGW-1 + nmea_float_t ang = 100000.; + char ref = 'T'; + if (!isEmpty(p)) + ang = atof(p); + p = strchr(p, ',') + 1; + if (!isEmpty(p)) + ref = *p; + p = strchr(p, ',') + 1; + nmea_float_t spd = 100000.; + if (!isEmpty(p)) + spd = atof(p); + p = strchr(p, ',') + 1; + char units = 'N'; + if (!isEmpty(p)) + units = *p; + p = strchr(p, ',') + 1; + char stat = 'A'; + if (!isEmpty(p)) + stat = *p; // last before checksum + if (units == 'K') { + spd /= 1.6; + units = 'M'; + } + if (units == 'M') { + spd *= 5280. / 6000.; + units = 'N'; + } + if (ang > 180.) + ang -= 360.; + if (ref == 'R') { + if (ang < 1000. && stat == 'A') + newDataValue(NMEA_AWA, ang); + if (spd < 1000. && stat == 'A') + newDataValue(NMEA_AWS, spd); + } else { + if (ang < 1000. && stat == 'A') + newDataValue(NMEA_TWA, ang); + if (spd < 1000. && stat == 'A') + newDataValue(NMEA_TWS, spd); + } + + } else if (!strcmp(thisSentence, "RMB")) { //*****************************RMB + // from Actisense NGW-1 from SH CP150C + p = strchr(p, ',') + 1; // skip status + nmea_float_t xte = 100000.; + char xteDir = 'X'; + if (!isEmpty(p)) + xte = atof(p); + p = strchr(p, ',') + 1; + if (!isEmpty(p)) + xteDir = *p; + p = strchr(p, ',') + 1; + if (xte < 10000. && xteDir != 'X') { + if (xteDir == 'L') + xte *= -1.; + newDataValue(NMEA_XTE, xte); + } + if (!isEmpty(p)) + parseStr(toID, p, NMEA_MAX_WP_ID); + p = strchr(p, ',') + 1; + if (!isEmpty(p)) + parseStr(fromID, p, NMEA_MAX_WP_ID); + p = strchr(p, ',') + 1; + nmea_float_t latitudeWP = + 0; // All the same position data for the next way point + nmea_float_t longitudeWP = 0; + int32_t latitude_fixedWP = 0; + int32_t longitude_fixedWP = 0; + nmea_float_t latitudeDegreesWP = 0; + nmea_float_t longitudeDegreesWP = 0; + char latWP = 'N'; + char lonWP = 'W'; + + // parse out both latitude and direction for WayPoint, then go to next + // field, or fail + if (!isEmpty(p)) { + if (!parseCoord(p, &latitudeDegreesWP, &latitudeWP, &latitude_fixedWP, + &latWP)) + return false; + else + newDataValue(NMEA_LATWP, latitudeDegreesWP); + } + p = strchr(p, ',') + 1; + p = strchr(p, ',') + 1; + // parse out both longitude and direction for WayPoint, then go to next + // field, or fail + if (!isEmpty(p)) { + if (!parseCoord(p, &longitudeDegreesWP, &longitudeWP, &longitude_fixedWP, + &lonWP)) + return false; + else + newDataValue(NMEA_LONWP, longitudeDegreesWP); + } + p = strchr(p, ',') + 1; + p = strchr(p, ',') + 1; + p = strchr(p, ',') + 1; + p = strchr(p, ',') + 1; + if (!isEmpty(p)) + newDataValue(NMEA_DISTWP, atof(p)); + p = strchr(p, ',') + 1; + if (!isEmpty(p)) + newDataValue(NMEA_COGWP, atof(p)); + p = strchr(p, ',') + 1; + if (!isEmpty(p)) + newDataValue(NMEA_VMGWP, atof(p)); // skip arrival flag + + } else if (!strcmp(thisSentence, "ROT")) { //*****************************ROT + return false; + + } else if (!strcmp(thisSentence, "RPM")) { //*****************************RPM + return false; + + } else if (!strcmp(thisSentence, "RSA")) { //*****************************RSA + // from Actisense NGW-1 + return false; + + } else if (!strcmp(thisSentence, "TXT")) { //*****************************TXT if (!isEmpty(p)) txtTot = atoi(p); p = strchr(p, ',') + 1; @@ -216,12 +404,128 @@ bool Adafruit_GPS::parse(char *nmea) { p = strchr(p, ',') + 1; if (!isEmpty(p)) parseStr(txtTXT, p, 61); // copy the text to NMEA TXT max of 61 characters + + } else if (!strcmp(thisSentence, "VDR")) { //*****************************VDR + // from Actisense NGW-1 + return false; + + } else if (!strcmp(thisSentence, "VHW")) { //*****************************VHW + // from Actisense NGW-1 + if (!isEmpty(p)) + newDataValue(NMEA_HDT, atof(p)); + p = strchr(p, ',') + 1; + p = strchr(p, ',') + 1; + if (!isEmpty(p)) + newDataValue(NMEA_HDG, atof(p)); + p = strchr(p, ',') + 1; + p = strchr(p, ',') + 1; + if (!isEmpty(p)) + newDataValue(NMEA_VTW, atof(p)); // skip the other units + + } else if (!strcmp(thisSentence, "VLW")) { //*****************************VLW + // from Actisense NGW-1 + if (!isEmpty(p)) + newDataValue(NMEA_LOG, atof(p)); + p = strchr(p, ',') + 1; + p = strchr(p, ',') + 1; + if (!isEmpty(p)) + newDataValue(NMEA_LOGR, atof(p)); // skip the other units + + } else if (!strcmp(thisSentence, "VPW")) { //*****************************VPW + // knots, metres/s coerced to knots + nmea_float_t vmg = 100000.; + if (!isEmpty(p)) + vmg = atof(p); + p = strchr(p, ',') + 1; + p = strchr(p, ',') + 1; + if (!isEmpty(p)) + vmg = atof(p) * 0.3048 * 3600. / 6000.; // skip units + if (vmg < 1000.) + newDataValue(NMEA_VMG, vmg); + } else if (!strcmp(thisSentence, "VTG")) { //*****************************VTG + // from Actisense NGW-1 from SH CP150C + return false; + + } else if (!strcmp(thisSentence, "VWR")) { //*****************************VWR + // from Actisense NGW-1 + nmea_float_t ang = 1000.; + if (!isEmpty(p)) + ang = atof(p); + p = strchr(p, ',') + 1; + char ref = ' '; + if (!isEmpty(p)) + ref = *p; + p = strchr(p, ',') + 1; + if (ref == 'L') + ang *= -1; + if (ang < 1000.) + newDataValue(NMEA_AWA, ang); + nmea_float_t ws = 0.0; + char units = 'X'; + if (!isEmpty(p)) + ws = atof(p); + p = strchr(p, ',') + 1; // knots + if (!isEmpty(p)) + units = *p; + p = strchr(p, ',') + 1; + if (!isEmpty(p)) + ws = atof(p); + p = strchr(p, ',') + 1; // meters / second + if (!isEmpty(p)) + units = *p; + p = strchr(p, ',') + 1; // M + if (!isEmpty(p)) + ws = atof(p); + p = strchr(p, ',') + 1; // kilometers / hour can be converted back to knots + if (!isEmpty(p)) + units = *p; // last before checksum + if (units == 'M') { + ws *= 3.6; + units = 'K'; + } // convert m/s to km/h + if (units == 'K') { + ws /= 1.6; + units = 'M'; + } // convert km/h to miles / h + if (units == 'M') { + ws *= 5280. / 6000.; + units = 'N'; + } // convert miles / hr to knots + if (units == 'N') + newDataValue(NMEA_AWS, ws); // store the final result + + } else if (!strcmp(thisSentence, "WCV")) { //*****************************WCV + // from SH CP150C + if (!isEmpty(p)) + newDataValue(NMEA_VMGWP, atof(p)); // skip the rest + + } else if (!strcmp(thisSentence, "XTE")) { //*****************************XTE + // from Actisense NGW-1 from SH CP150C + p = strchr(p, ',') + 1; // skip status 1 + p = strchr(p, ',') + 1; // skip status 2 + nmea_float_t xte = 100000.; + char xteDir = 'X'; + if (!isEmpty(p)) + xte = atof(p); + p = strchr(p, ',') + 1; + if (!isEmpty(p)) + xteDir = *p; + p = strchr(p, ',') + 1; + if (xte < 10000. && xteDir != 'X') { + if (xteDir == 'L') + xte *= -1.; + newDataValue(NMEA_XTE, xte); + } // skip units + + } else if (!strcmp(thisSentence, "ZDA")) { //*****************************ZDA + // from Actisense NGW-1 + return false; } #endif // NMEA_EXTENSIONS - // we dont parse the remaining, yet! - else - return false; + else { + return false; // didn't find the required sentence definition + } // Record the successful parsing of where the last data came from and when strcpy(lastSource, thisSource); @@ -229,3 +533,298 @@ bool Adafruit_GPS::parse(char *nmea) { lastUpdate = millis(); return true; } + +/**************************************************************************/ +/*! + @brief Check an NMEA string for basic format, valid source ID and valid + and valid sentence ID. Update the values of thisCheck, thisSource and + thisSentence. + @param nmea Pointer to the NMEA string + @return True if well formed, false if it has problems +*/ +/**************************************************************************/ +boolean Adafruit_GPS::check(char *nmea) { + thisCheck = 0; // new check + if (*nmea != '$' && *nmea != '!') + return false; // doesn't start with $ or ! + else + thisCheck += NMEA_HAS_DOLLAR; + // do checksum check -- first look if we even have one -- ignore all but last + // * + char *ast = nmea; // not strchr(nmea,'*'); for first * + while (*ast) + ast++; // go to the end + while (*ast != '*' && ast > nmea) + ast--; // then back to * if it's there + if (*ast != '*') + return false; // there is no asterisk + else { + uint16_t sum = parseHex(*(ast + 1)) * 16; // extract checksum + sum += parseHex(*(ast + 2)); + char *p = nmea; // check checksum + for (char *p1 = p + 1; p1 < ast; p1++) + sum ^= *p1; + if (sum != 0) + return false; // bad checksum :( + else + thisCheck += NMEA_HAS_CHECKSUM; + } + // extract source of variable length + char *p = nmea + 1; + const char *src = tokenOnList(p, sources); + if (src) { + strcpy(thisSource, src); + thisCheck += NMEA_HAS_SOURCE; + } else + return false; + p += strlen(src); + // extract sentence id and check if parsed + const char *snc = tokenOnList(p, sentences_parsed); + if (snc) { + strcpy(thisSentence, snc); + thisCheck += NMEA_HAS_SENTENCE_P + NMEA_HAS_SENTENCE; + } else { // check if known + snc = tokenOnList(p, sentences_known); + if (snc) { + strcpy(thisSentence, snc); + thisCheck += NMEA_HAS_SENTENCE; + return false; + } + } + return true; // passed all the tests +} + +/**************************************************************************/ +/*! + @brief Check if a token at the start of a string is on a list. + @param token Pointer to the string + @param list A list of strings, with the final entry starting "ZZ" + @return Pointer to the found token, or NULL if it fails +*/ +/**************************************************************************/ +const char *Adafruit_GPS::tokenOnList(char *token, const char **list) { + int i = 0; // index in the list + while (strncmp(list[i], "ZZ", 2) && + i < 1000) { // stop at terminator and don't crash without it + // test for a match on the sentence name + if (!strncmp((const char *)list[i], (const char *)token, strlen(list[i]))) + return list[i]; + i++; + } + return NULL; // couldn't find a match +} + +/**************************************************************************/ +/*! + @brief Check if an NMEA string is valid and is on a list, perhaps to + decide if it should be passed to a particular NMEA device. + @param nmea Pointer to the NMEA string + @param list A list of strings, with the final entry "ZZ" + @return True if on the list, false if it fails check or is not on the list +*/ +/**************************************************************************/ +bool Adafruit_GPS::onList(char *nmea, const char **list) { + if (!check(nmea)) // sets thisSentence if valid + return false; // not a valid sentence + // stop at terminator with first two letters ZZ and don't crash without it + for (int i = 0; strncmp(list[i], "ZZ", 2) && i < 1000; i++) { + // test for a match on the sentence name + if (!strcmp((const char *)list[i], (const char *)thisSentence)) + return true; + } + return false; // couldn't find a match +} + +/**************************************************************************/ +/*! + @brief Parse a part of an NMEA string for lat or lon angle and direction. + Works for either DDMM.mmmm,N (latitude) or DDDMM.mmmm,W (longitude) format. + Insensitive to number of decimal places present. Only fills the variables + if it succeeds and the variable pointer is not NULL. This allows calling + to fill only the variables of interest. Does rudimentary validation on + angle range. + + Supersedes private functions parseLat(), parseLon(), parseLatDir(), + parseLonDir(), all previously called from parse(). + @param pStart Pointer to the location of the token in the NMEA string + @param angle Pointer to the angle to fill with value in degrees/minutes as + received from the GPS (DDDMM.MMMM), unsigned + @param angle_fixed Pointer to the fix point version latitude in decimal + degrees * 10000000, signed + @param angleDegrees Pointer to the angle to fill with decimal degrees, + signed. As actual double on SAMD, etc. resolution is better than the + fixed point version. + @param dir Pointer to character to fill the direction N/S/E/W + @return true if successful, false if failed or no value +*/ +/**************************************************************************/ +bool Adafruit_GPS::parseCoord(char *pStart, nmea_float_t *angleDegrees, + nmea_float_t *angle, int32_t *angle_fixed, + char *dir) { + char *p = pStart; + if (!isEmpty(p)) { + // get the number in DDDMM.mmmm format and break into components + char degreebuff[10]; + char *e = strchr(p, '.'); + if (e == NULL || e - p > 6) + return false; // no decimal point in range + strncpy(degreebuff, p, e - p); // get DDDMM + long dddmm = atol(degreebuff); + long degrees = (dddmm / 100); // truncate the minutes + long minutes = dddmm - degrees * 100; // remove the degrees + p = e; // start from the decimal point + nmea_float_t decminutes = atof(e); // the fraction after the decimal point + p = strchr(p, ',') + 1; // go to the next field + + // get the NSEW direction as a character + char nsew = 'X'; + if (!isEmpty(p)) + nsew = *p; // field is not empty + else + return false; // no direction provided + + // set the various numerical formats to their values + long fixed = degrees * 10000000 + (minutes * 10000000) / 60 + + (decminutes * 10000000) / 60; + nmea_float_t ang = degrees * 100 + minutes + decminutes; + nmea_float_t deg = fixed / (nmea_float_t)10000000.; + if (nsew == 'S' || + nsew == 'W') { // fixed and deg are signed, but DDDMM.mmmm is not + fixed = -fixed; + deg = -deg; + } + + // reject directions that are not NSEW + if (nsew != 'N' && nsew != 'S' && nsew != 'E' && nsew != 'W') + return false; + + // reject angles that are out of range + if (nsew == 'N' || nsew == 'S') + if(abs(deg) > 90) return false; + if(abs(deg) > 180) return false; + + // store in locations passed as args + if (angle != NULL) + *angle = ang; + if (angle_fixed != NULL) + *angle_fixed = fixed; + if (angleDegrees != NULL) + *angleDegrees = deg; + if (dir != NULL) + *dir = nsew; + } else + return false; // no number + return true; +} + +/**************************************************************************/ +/*! + @brief Parse a string token from pointer p to the next comma, asterisk + or end of string. + @param buff Pointer to the buffer to store the string in + @param p Pointer into a string + @param n Max permitted size of string including terminating 0 + @return Pointer to the string buffer +*/ +/**************************************************************************/ +char *Adafruit_GPS::parseStr(char *buff, char *p, int n) { + char *e = strchr(p, ','); + int len = 0; + if (e) { + len = min(e - p, n - 1); + strncpy(buff, p, len); // copy up to the comma + buff[len] = 0; + } else { + e = strchr(p, '*'); + if (e) { + len = min(e - p, n - 1); + strncpy(buff, p, len); // or up to the * + buff[e - p] = 0; + } else { + len = min((int)strlen(p), n - 1); + strncpy(buff, p, len); // or to the end or max capacity + } + } + return buff; +} + +/**************************************************************************/ +/*! + @brief Parse a part of an NMEA string for time. Independent of number + of decimal places after the '.' + @param p Pointer to the location of the token in the NMEA string + @return true if successful, false otherwise +*/ +/**************************************************************************/ +bool Adafruit_GPS::parseTime(char *p) { + if (!isEmpty(p)) { // get time + uint32_t time = atol(p); + hour = time / 10000; + minute = (time % 10000) / 100; + seconds = (time % 100); + p = strchr(p, '.'); + milliseconds = atof(p) * 1000; + lastTime = sentTime; + return true; + } + return false; +} + +/**************************************************************************/ +/*! + @brief Parse a part of an NMEA string for whether there is a fix + @param p Pointer to the location of the token in the NMEA string + @return True if we parsed it, false if it has invalid data +*/ +/**************************************************************************/ +boolean Adafruit_GPS::parseFix(char *p) { + if (!isEmpty(p)) { + if (p[0] == 'A') { + fix = true; + lastFix = sentTime; + } else if (p[0] == 'V') + fix = false; + else + return false; + return true; + } + return false; +} + +/**************************************************************************/ +/*! + @brief Is the field empty, or should we try conversion? Won't work + for a text field that starts with an asterisk or a comma, but that + probably violates the NMEA-183 standard. + @param pStart Pointer to the location of the token in the NMEA string + @return true if empty field, false if something there +*/ +/**************************************************************************/ +bool Adafruit_GPS::isEmpty(char *pStart) { + if (',' != *pStart && '*' != *pStart && pStart != NULL) + return false; + else + return true; +} + +/**************************************************************************/ +/*! + @brief Parse a hex character and return the appropriate decimal value + @param c Hex character, e.g. '0' or 'B' + @return Integer value of the hex character. Returns 0 if c is not a proper + character +*/ +/**************************************************************************/ +// read a Hex value and return the decimal equivalent +uint8_t Adafruit_GPS::parseHex(char c) { + if (c < '0') + return 0; + if (c <= '9') + return c - '0'; + if (c < 'A') + return 0; + if (c <= 'F') + return (c - 'A') + 10; + // if (c > 'F') + return 0; +} From 0fca64d08295ce914c839ce99701f8baac7b1ab3 Mon Sep 17 00:00:00 2001 From: Rick Sellens Date: Wed, 29 Jan 2020 10:27:05 -0500 Subject: [PATCH 08/13] Comment out old Lat Lon parsing new parse() uses generalized parseCoord() --- src/Adafruit_GPS.cpp | 136 +++++++++++++++++++++---------------------- src/Adafruit_GPS.h | 8 +-- 2 files changed, 72 insertions(+), 72 deletions(-) diff --git a/src/Adafruit_GPS.cpp b/src/Adafruit_GPS.cpp index a1e72b4..a0364b4 100644 --- a/src/Adafruit_GPS.cpp +++ b/src/Adafruit_GPS.cpp @@ -46,24 +46,24 @@ static bool strStartsWith(const char *str, const char *prefix); @param p Pointer to the location of the token in the NMEA string */ /**************************************************************************/ -void Adafruit_GPS::parseLat(char *p) { - char degreebuff[10]; - if (!isEmpty(p)) { - strncpy(degreebuff, p, 2); - p += 2; - degreebuff[2] = '\0'; - long degree = atol(degreebuff) * 10000000; - strncpy(degreebuff, p, 2); // minutes - p += 3; // skip decimal point - strncpy(degreebuff + 2, p, 4); - degreebuff[6] = '\0'; - long minutes = 50 * atol(degreebuff) / 3; - latitude_fixed = degree + minutes; - latitude = degree / 100000 + minutes * 0.000006F; - latitudeDegrees = (latitude - 100 * int(latitude / 100)) / 60.0f; - latitudeDegrees += int(latitude / 100); - } -} +// void Adafruit_GPS::parseLat(char *p) { +// char degreebuff[10]; +// if (!isEmpty(p)) { +// strncpy(degreebuff, p, 2); +// p += 2; +// degreebuff[2] = '\0'; +// long degree = atol(degreebuff) * 10000000; +// strncpy(degreebuff, p, 2); // minutes +// p += 3; // skip decimal point +// strncpy(degreebuff + 2, p, 4); +// degreebuff[6] = '\0'; +// long minutes = 50 * atol(degreebuff) / 3; +// latitude_fixed = degree + minutes; +// latitude = degree / 100000 + minutes * 0.000006F; +// latitudeDegrees = (latitude - 100 * int(latitude / 100)) / 60.0f; +// latitudeDegrees += int(latitude / 100); +// } +// } /**************************************************************************/ /*! @@ -72,20 +72,20 @@ void Adafruit_GPS::parseLat(char *p) { @return True if we parsed it, false if it has invalid data */ /**************************************************************************/ -bool Adafruit_GPS::parseLatDir(char *p) { - if (p[0] == 'S') { - lat = 'S'; - latitudeDegrees *= -1.0f; - latitude_fixed *= -1; - } else if (p[0] == 'N') { - lat = 'N'; - } else if (p[0] == ',') { - lat = 0; - } else { - return false; - } - return true; -} +// bool Adafruit_GPS::parseLatDir(char *p) { +// if (p[0] == 'S') { +// lat = 'S'; +// latitudeDegrees *= -1.0f; +// latitude_fixed *= -1; +// } else if (p[0] == 'N') { +// lat = 'N'; +// } else if (p[0] == ',') { +// lat = 0; +// } else { +// return false; +// } +// return true; +// } /**************************************************************************/ /*! @@ -93,26 +93,26 @@ bool Adafruit_GPS::parseLatDir(char *p) { @param p Pointer to the location of the token in the NMEA string */ /**************************************************************************/ -void Adafruit_GPS::parseLon(char *p) { - int32_t degree; - long minutes; - char degreebuff[10]; - if (!isEmpty(p)) { - strncpy(degreebuff, p, 3); - p += 3; - degreebuff[3] = '\0'; - degree = atol(degreebuff) * 10000000; - strncpy(degreebuff, p, 2); // minutes - p += 3; // skip decimal point - strncpy(degreebuff + 2, p, 4); - degreebuff[6] = '\0'; - minutes = 50 * atol(degreebuff) / 3; - longitude_fixed = degree + minutes; - longitude = degree / 100000 + minutes * 0.000006F; - longitudeDegrees = (longitude - 100 * int(longitude / 100)) / 60.0f; - longitudeDegrees += int(longitude / 100); - } -} +// void Adafruit_GPS::parseLon(char *p) { +// int32_t degree; +// long minutes; +// char degreebuff[10]; +// if (!isEmpty(p)) { +// strncpy(degreebuff, p, 3); +// p += 3; +// degreebuff[3] = '\0'; +// degree = atol(degreebuff) * 10000000; +// strncpy(degreebuff, p, 2); // minutes +// p += 3; // skip decimal point +// strncpy(degreebuff + 2, p, 4); +// degreebuff[6] = '\0'; +// minutes = 50 * atol(degreebuff) / 3; +// longitude_fixed = degree + minutes; +// longitude = degree / 100000 + minutes * 0.000006F; +// longitudeDegrees = (longitude - 100 * int(longitude / 100)) / 60.0f; +// longitudeDegrees += int(longitude / 100); +// } +// } /**************************************************************************/ /*! @@ -121,22 +121,22 @@ void Adafruit_GPS::parseLon(char *p) { @return True if we parsed it, false if it has invalid data */ /**************************************************************************/ -bool Adafruit_GPS::parseLonDir(char *p) { - if (!isEmpty(p)) { - if (p[0] == 'W') { - lon = 'W'; - longitudeDegrees *= -1.0f; - longitude_fixed *= -1; - } else if (p[0] == 'E') { - lon = 'E'; - } else if (p[0] == ',') { - lon = 0; - } else { - return false; - } - } - return true; -} +// bool Adafruit_GPS::parseLonDir(char *p) { +// if (!isEmpty(p)) { +// if (p[0] == 'W') { +// lon = 'W'; +// longitudeDegrees *= -1.0f; +// longitude_fixed *= -1; +// } else if (p[0] == 'E') { +// lon = 'E'; +// } else if (p[0] == ',') { +// lon = 0; +// } else { +// return false; +// } +// } +// return true; +// } /**************************************************************************/ /*! diff --git a/src/Adafruit_GPS.h b/src/Adafruit_GPS.h index 601ca72..a0c0dea 100644 --- a/src/Adafruit_GPS.h +++ b/src/Adafruit_GPS.h @@ -236,10 +236,10 @@ public: #endif // NMEA_EXTENSIONS private: - void parseLat(char *); - bool parseLatDir(char *); - void parseLon(char *); - bool parseLonDir(char *); +// void parseLat(char *); +// bool parseLatDir(char *); +// void parseLon(char *); +// bool parseLonDir(char *); // NMEA_data.cpp void data_init(); // NMEA_parse.cpp From 1d58e4e66e6a4c66be91e8ed41889047880224a5 Mon Sep 17 00:00:00 2001 From: Rick Sellens Date: Wed, 29 Jan 2020 10:35:00 -0500 Subject: [PATCH 09/13] run data_init() under common_init() --- src/Adafruit_GPS.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Adafruit_GPS.cpp b/src/Adafruit_GPS.cpp index a0364b4..e8aad41 100644 --- a/src/Adafruit_GPS.cpp +++ b/src/Adafruit_GPS.cpp @@ -437,6 +437,9 @@ void Adafruit_GPS::common_init(void) { 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 } /**************************************************************************/ From 7d889e3a9d966b2158f8150fdc83503a890fbaba Mon Sep 17 00:00:00 2001 From: Rick Sellens Date: Wed, 29 Jan 2020 12:03:25 -0500 Subject: [PATCH 10/13] Reorder and document Align the order in the .h with the order in the various .cpp Note that ! is also a valid starting character for an NMEA sentence. --- src/Adafruit_GPS.cpp | 318 +++++++++++++++++++++---------------------- src/Adafruit_GPS.h | 41 +++--- 2 files changed, 176 insertions(+), 183 deletions(-) diff --git a/src/Adafruit_GPS.cpp b/src/Adafruit_GPS.cpp index e8aad41..65e5c10 100644 --- a/src/Adafruit_GPS.cpp +++ b/src/Adafruit_GPS.cpp @@ -140,44 +140,144 @@ static bool strStartsWith(const char *str, const char *prefix); /**************************************************************************/ /*! - @brief Time in seconds since the last position fix was obtained. Will - fail by rolling over to zero after one millis() cycle, about 6-1/2 weeks. - @return nmea_float_t value in seconds since last fix. + @brief 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 */ /**************************************************************************/ -nmea_float_t Adafruit_GPS::secondsSinceFix() { - return (millis() - lastFix) / 1000.; +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 Time in seconds since the last GPS time was obtained. Will fail - by rolling over to zero after one millis() cycle, about 6-1/2 weeks. - @return nmea_float_t value in seconds since last GPS time. + @brief Constructor when using SoftwareSerial + @param ser Pointer to SoftwareSerial device */ /**************************************************************************/ -nmea_float_t Adafruit_GPS::secondsSinceTime() { - return (millis() - lastTime) / 1000.; +#if (defined(__AVR__) || defined(ESP8266)) && defined(USE_SW_SERIAL) +Adafruit_GPS::Adafruit_GPS(SoftwareSerial *ser) { + common_init(); // Set everything to common state, then... + gpsSwSerial = ser; // ...override gpsSwSerial with value passed. +} +#endif + +/**************************************************************************/ +/*! + @brief Constructor when using HardwareSerial + @param ser Pointer to a HardwareSerial object +*/ +/**************************************************************************/ +Adafruit_GPS::Adafruit_GPS(HardwareSerial *ser) { + common_init(); // Set everything to common state, then... + gpsHwSerial = ser; // ...override gpsHwSerial with value passed. } /**************************************************************************/ /*! - @brief Time in seconds since the last GPS date was obtained. Will fail - by rolling over to zero after one millis() cycle, about 6-1/2 weeks. - @return nmea_float_t value in seconds since last GPS date. + @brief Constructor when using I2C + @param theWire Pointer to an I2C TwoWire object */ /**************************************************************************/ -nmea_float_t Adafruit_GPS::secondsSinceDate() { - return (millis() - lastDate) / 1000.; +Adafruit_GPS::Adafruit_GPS(TwoWire *theWire) { + common_init(); // Set everything to common state, then... + gpsI2C = theWire; // ...override gpsI2C } /**************************************************************************/ /*! - @brief Fakes time of receipt of a sentence. Use between build() and parse() - to make the timing look like the sentence arrived from the GPS. + @brief 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 */ /**************************************************************************/ -void Adafruit_GPS::resetSentTime() { sentTime = millis(); } +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 Constructor when there are no communications attached +*/ +/**************************************************************************/ +Adafruit_GPS::Adafruit_GPS() { + common_init(); // Set everything to common state, then... + noComms = true; +} + +/**************************************************************************/ +/*! + @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 +#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 +} /**************************************************************************/ /*! @@ -354,147 +454,6 @@ char Adafruit_GPS::read(void) { return c; } -/**************************************************************************/ -/*! - @brief Constructor when using SoftwareSerial - @param ser Pointer to SoftwareSerial device -*/ -/**************************************************************************/ -#if (defined(__AVR__) || defined(ESP8266)) && defined(USE_SW_SERIAL) -Adafruit_GPS::Adafruit_GPS(SoftwareSerial *ser) { - common_init(); // Set everything to common state, then... - gpsSwSerial = ser; // ...override gpsSwSerial with value passed. -} -#endif - -/**************************************************************************/ -/*! - @brief Constructor when using HardwareSerial - @param ser Pointer to a HardwareSerial object -*/ -/**************************************************************************/ -Adafruit_GPS::Adafruit_GPS(HardwareSerial *ser) { - common_init(); // Set everything to common state, then... - gpsHwSerial = ser; // ...override gpsHwSerial with value passed. -} - -/**************************************************************************/ -/*! - @brief Constructor when using I2C - @param theWire Pointer to an I2C TwoWire object -*/ -/**************************************************************************/ -Adafruit_GPS::Adafruit_GPS(TwoWire *theWire) { - common_init(); // Set everything to common state, then... - gpsI2C = theWire; // ...override gpsI2C -} - -/**************************************************************************/ -/*! - @brief Constructor when using SPI - @param theSPI Pointer to an SPI device object - @param cspin The pin connected to the GPS CS, can be -1 if unused -*/ -/**************************************************************************/ -Adafruit_GPS::Adafruit_GPS(SPIClass *theSPI, int8_t cspin) { - common_init(); // Set everything to common state, then... - gpsSPI = theSPI; // ...override gpsSPI - gpsSPI_cs = cspin; -} - -/**************************************************************************/ -/*! - @brief Constructor when there are no communications attached -*/ -/**************************************************************************/ -Adafruit_GPS::Adafruit_GPS() { - common_init(); // Set everything to common state, then... - noComms = true; -} - -/**************************************************************************/ -/*! - @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 -#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 -} - -/**************************************************************************/ -/*! - @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 @@ -670,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 diff --git a/src/Adafruit_GPS.h b/src/Adafruit_GPS.h index a0c0dea..730fc26 100644 --- a/src/Adafruit_GPS.h +++ b/src/Adafruit_GPS.h @@ -21,8 +21,6 @@ */ /**************************************************************************/ -// Fllybob added lines 34,35 and 40,41 to add 100mHz logging capability - #ifndef _ADAFRUIT_GPS_H #define _ADAFRUIT_GPS_H @@ -67,7 +65,7 @@ /// type for resulting code from running check() typedef enum { NMEA_BAD = 0, ///< passed none of the checks - NMEA_HAS_DOLLAR = 1, ///< has a dollar sign in the first position + NMEA_HAS_DOLLAR = 1, ///< has a dollar sign or exclamation mark in the first position NMEA_HAS_CHECKSUM = 2, ///< has a valid checksum at the end NMEA_HAS_NAME = 4, ///< there is a token after the $ followed by a comma NMEA_HAS_SOURCE = 10, ///< has a recognized source ID @@ -81,6 +79,7 @@ typedef enum { */ class Adafruit_GPS : public Print { public: + // Adafruit_GPS.cpp bool begin(uint32_t baud_or_i2caddr); #if (defined(__AVR__) || defined(ESP8266)) && defined(USE_SW_SERIAL) @@ -90,28 +89,28 @@ public: Adafruit_GPS(TwoWire *theWire); // Constructor when using I2C Adafruit_GPS(SPIClass *theSPI, int8_t cspin); // Constructor when using SPI Adafruit_GPS(); // Constructor for no communications, just data storage + void common_init(void); virtual ~Adafruit_GPS(); - char *lastNMEA(void); - bool newNMEAreceived(); - void common_init(void); - - void sendCommand(const char *); - - void pause(bool b); - - char read(void); - size_t write(uint8_t); size_t available(void); - + size_t write(uint8_t); + char read(void); + void sendCommand(const char *); + bool newNMEAreceived(); + void pause(bool b); + char *lastNMEA(void); + bool waitForSentence(const char *wait, uint8_t max = MAXWAITSENTENCE, + bool usingInterrupts = false); + bool LOCUS_StartLogger(void); + bool LOCUS_StopLogger(void); + bool LOCUS_ReadStatus(void); + bool standby(void); + bool wakeup(void); nmea_float_t secondsSinceFix(); nmea_float_t secondsSinceTime(); nmea_float_t secondsSinceDate(); void resetSentTime(); - - bool wakeup(void); - bool standby(void); - + // NMEA_parse.cpp bool parse(char *); bool check(char *nmea); @@ -197,12 +196,6 @@ public: uint8_t fixquality_3d; ///< 3D fix quality (1, 3, 3 = Nofix, 2D fix, 3D fix) uint8_t satellites; ///< Number of satellites in use - bool waitForSentence(const char *wait, uint8_t max = MAXWAITSENTENCE, - bool usingInterrupts = false); - bool LOCUS_StartLogger(void); - bool LOCUS_StopLogger(void); - bool LOCUS_ReadStatus(void); - uint16_t LOCUS_serial; ///< Log serial number uint16_t LOCUS_records; ///< Log number of data record uint8_t LOCUS_type; ///< Log type, 0: Overlap, 1: FullStop From 8ae6a7b75acf5b67758b98dedd810fafcf26b1e8 Mon Sep 17 00:00:00 2001 From: Rick Sellens Date: Wed, 29 Jan 2020 12:57:20 -0500 Subject: [PATCH 11/13] clang --- src/Adafruit_GPS.h | 46 +++++++++++++++++++++++----------------------- src/NMEA_build.cpp | 27 ++++++++++++++++----------- src/NMEA_data.cpp | 17 ++++++++++------- src/NMEA_parse.cpp | 20 +++++++++++--------- 4 files changed, 60 insertions(+), 50 deletions(-) diff --git a/src/Adafruit_GPS.h b/src/Adafruit_GPS.h index 730fc26..d6300ac 100644 --- a/src/Adafruit_GPS.h +++ b/src/Adafruit_GPS.h @@ -30,13 +30,13 @@ little memory as possible for GPS functionality only. The ARDUINO_ARCH_AVR test should leave it out of any compilations for the UNO and similar. */ #ifndef NMEA_EXTRAS // inject on the compile command line to force extensions - #ifndef ARDUINO_ARCH_AVR - #define NMEA_EXTENSIONS ///< if defined will include more NMEA sentences - #endif +#ifndef ARDUINO_ARCH_AVR +#define NMEA_EXTENSIONS ///< if defined will include more NMEA sentences +#endif #else - #if(NMEA_EXTRAS > 0) - #define NMEA_EXTENSIONS ///< if defined will include more NMEA sentences - #endif +#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 @@ -64,8 +64,9 @@ /// type for resulting code from running check() typedef enum { - NMEA_BAD = 0, ///< passed none of the checks - NMEA_HAS_DOLLAR = 1, ///< has a dollar sign or exclamation mark in the first position + NMEA_BAD = 0, ///< passed none of the checks + NMEA_HAS_DOLLAR = + 1, ///< has a dollar sign or exclamation mark in the first position NMEA_HAS_CHECKSUM = 2, ///< has a valid checksum at the end NMEA_HAS_NAME = 4, ///< there is a token after the $ followed by a comma NMEA_HAS_SOURCE = 10, ///< has a recognized source ID @@ -91,7 +92,7 @@ public: Adafruit_GPS(); // Constructor for no communications, just data storage void common_init(void); virtual ~Adafruit_GPS(); - + size_t available(void); size_t write(uint8_t); char read(void); @@ -100,7 +101,7 @@ public: void pause(bool b); char *lastNMEA(void); bool waitForSentence(const char *wait, uint8_t max = MAXWAITSENTENCE, - bool usingInterrupts = false); + bool usingInterrupts = false); bool LOCUS_StartLogger(void); bool LOCUS_StopLogger(void); bool LOCUS_ReadStatus(void); @@ -110,7 +111,7 @@ public: nmea_float_t secondsSinceTime(); nmea_float_t secondsSinceDate(); void resetSentTime(); - + // NMEA_parse.cpp bool parse(char *); bool check(char *nmea); @@ -123,7 +124,7 @@ public: char ref = 'R'); #endif void addChecksum(char *buff); - + // NMEA_data.cpp void newDataValue(nmea_index_t tag, nmea_float_t v); #ifdef NMEA_EXTENSIONS @@ -229,10 +230,10 @@ public: #endif // NMEA_EXTENSIONS private: -// void parseLat(char *); -// bool parseLatDir(char *); -// void parseLon(char *); -// bool parseLonDir(char *); + // void parseLat(char *); + // bool parseLatDir(char *); + // void parseLon(char *); + // bool parseLonDir(char *); // NMEA_data.cpp void data_init(); // NMEA_parse.cpp @@ -246,8 +247,8 @@ private: bool isEmpty(char *pStart); // used by check() for validity tests, room for future expansion - const char *sources[6] = {"II", "WI", "GP", "GN", - "P", "ZZZ"}; ///< valid source ids + const char *sources[6] = {"II", "WI", "GP", + "GN", "P", "ZZZ"}; ///< valid source ids #ifdef NMEA_EXTENSIONS const char *sentences_parsed[20] = @@ -260,11 +261,10 @@ private: "RPM", "RSA", "VDR", "VTG", "ZDA", "ZZZ"}; ///< known, but not parseable #else // make the lists short to save memory const char - *sentences_parsed[5] = - { - "GGA", "GLL", "GSA", "RMC", "ZZZ"}; ///< parseable sentence ids - const char *sentences_known[4] = { - "DBT", "HDM", "HDT", "ZZZ"}; ///< known, but not parseable + *sentences_parsed[5] = {"GGA", "GLL", "GSA", "RMC", + "ZZZ"}; ///< parseable sentence ids + const char *sentences_known[4] = {"DBT", "HDM", "HDT", + "ZZZ"}; ///< known, but not parseable #endif // Make all of these times far in the past by setting them near the middle of diff --git a/src/NMEA_build.cpp b/src/NMEA_build.cpp index 3cfc1f7..8b78139 100644 --- a/src/NMEA_build.cpp +++ b/src/NMEA_build.cpp @@ -44,8 +44,8 @@ extension to building proprietary sentences like $PMTK220,100*2F. build() will not work properly in an environment that does not support - the %f floating point formatter in sprintf(), and will return NULL. - Floating point arguments to sprintf() are explicitly cast to double to + 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 @@ -72,7 +72,8 @@ /**************************************************************************/ char *Adafruit_GPS::build(char *nmea, const char *thisSource, const char *thisSentence, char ref) { - sprintf(nmea, "%6.2f", (double)123.45); // fail if sprintf() doesn't handle floats + sprintf(nmea, "%6.2f", + (double)123.45); // fail if sprintf() doesn't handle floats if (strcmp(nmea, "123.45")) return NULL; *nmea = '$'; @@ -112,9 +113,10 @@ char *Adafruit_GPS::build(char *nmea, const char *thisSource, // 14) Differential reference station ID, 0000-1023 // 15) Checksum sprintf(p, "%09.2f,%09.4f,%c,%010.4f,%c,%d,%02d,%f,%f,M,%f,M,,", - (double)hour * 10000L + minute * 100L + seconds + milliseconds / 1000., - (double)latitude, lat, (double)longitude, lon, fixquality, satellites, - (double)HDOP, (double)altitude, (double)geoidheight); + (double)hour * 10000L + minute * 100L + seconds + + milliseconds / 1000., + (double)latitude, lat, (double)longitude, lon, fixquality, + satellites, (double)HDOP, (double)altitude, (double)geoidheight); } else if (!strcmp(thisSentence, "GLL")) { //*****************************GLL // GLL Geographic Position – Latitude/Longitude @@ -129,8 +131,9 @@ char *Adafruit_GPS::build(char *nmea, const char *thisSource, // 6) Status A - Data Valid, V - Data Invalid // 7) Checksum sprintf(p, "%09.4f,%c,%010.4f,%c,%09.2f,A", (double)latitude, lat, - (double)longitude, lon, (double)hour * 10000L + minute * 100L + - seconds + milliseconds / 1000.); + (double)longitude, lon, + (double)hour * 10000L + minute * 100L + seconds + + milliseconds / 1000.); } else if (!strcmp(thisSentence, "GSA")) { //*****************************GSA // GSA GPS DOP and active satellites @@ -168,9 +171,11 @@ char *Adafruit_GPS::build(char *nmea, const char *thisSource, // 11) E or W // 12) Checksum sprintf(p, "%09.2f,A,%09.4f,%c,%010.4f,%c,%f,%f,%06d,%f,%c", - (double)hour * 10000L + minute * 100L + seconds + milliseconds / 1000., - (double)latitude, lat, (double)longitude, lon, (double)speed, - (double)angle, day * 10000 + month * 100 + year, (double)magvariation, mag); + (double)hour * 10000L + minute * 100L + seconds + + milliseconds / 1000., + (double)latitude, lat, (double)longitude, lon, (double)speed, + (double)angle, day * 10000 + month * 100 + year, + (double)magvariation, mag); } else if (!strcmp(thisSentence, "APB")) { //*****************************APB // APB Autopilot Sentence "B" diff --git a/src/NMEA_data.cpp b/src/NMEA_data.cpp index c5d0c15..6a76200 100644 --- a/src/NMEA_data.cpp +++ b/src/NMEA_data.cpp @@ -58,8 +58,9 @@ void Adafruit_GPS::newDataValue(nmea_index_t idx, nmea_float_t v) { 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); + 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 @@ -437,7 +438,8 @@ void Adafruit_GPS::removeHistory(nmea_index_t idx) { /**************************************************************************/ void Adafruit_GPS::showDataValue(nmea_index_t idx, int n) { Serial.print("idx: "); - if(idx < 10) Serial.print(" "); + if(idx < 10) + Serial.print(" "); Serial.print(idx); Serial.print(", "); Serial.print(val[idx].label); @@ -458,8 +460,9 @@ void Adafruit_GPS::showDataValue(nmea_index_t idx, int n) { 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 + 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]); } @@ -471,7 +474,7 @@ void Adafruit_GPS::showDataValue(nmea_index_t idx, int n) { Serial.print(", lat: "); Serial.print(lat); Serial.print(", latitudeDegrees: "); - Serial.print(latitudeDegrees,8); + Serial.print(latitudeDegrees, 8); Serial.print(", latitude_fixed: "); Serial.println(latitude_fixed); } @@ -481,7 +484,7 @@ void Adafruit_GPS::showDataValue(nmea_index_t idx, int n) { Serial.print(", lon: "); Serial.print(lon); Serial.print(", longitudeDegrees: "); - Serial.print(longitudeDegrees,8); + Serial.print(longitudeDegrees, 8); Serial.print(", longitude_fixed: "); Serial.println(longitude_fixed); } diff --git a/src/NMEA_parse.cpp b/src/NMEA_parse.cpp index 0b98b9d..36e2621 100644 --- a/src/NMEA_parse.cpp +++ b/src/NMEA_parse.cpp @@ -41,8 +41,8 @@ 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 + 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 @@ -641,7 +641,7 @@ bool Adafruit_GPS::onList(char *nmea, const char **list) { 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 + to fill only the variables of interest. Does rudimentary validation on angle range. Supersedes private functions parseLat(), parseLon(), parseLatDir(), @@ -652,7 +652,7 @@ bool Adafruit_GPS::onList(char *nmea, const char **list) { @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 + 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 @@ -662,7 +662,7 @@ 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)) { + if (!isEmpty(p)) { // get the number in DDDMM.mmmm format and break into components char degreebuff[10]; char *e = strchr(p, '.'); @@ -674,7 +674,7 @@ bool Adafruit_GPS::parseCoord(char *pStart, nmea_float_t *angleDegrees, 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 + p = strchr(p, ',') + 1; // go to the next field // get the NSEW direction as a character char nsew = 'X'; @@ -697,11 +697,13 @@ bool Adafruit_GPS::parseCoord(char *pStart, nmea_float_t *angleDegrees, // 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; + if(abs(deg) > 90) + return false; + if(abs(deg) > 180) + return false; // store in locations passed as args if (angle != NULL) From 1afe1df764ada08eb8f639f0d8b7c6aeca3502ef Mon Sep 17 00:00:00 2001 From: Rick Sellens Date: Wed, 29 Jan 2020 13:10:43 -0500 Subject: [PATCH 12/13] clang --- src/Adafruit_GPS.h | 11 +++++------ src/NMEA_data.cpp | 8 ++++---- src/NMEA_parse.cpp | 4 ++-- 3 files changed, 11 insertions(+), 12 deletions(-) diff --git a/src/Adafruit_GPS.h b/src/Adafruit_GPS.h index d6300ac..fe9baee 100644 --- a/src/Adafruit_GPS.h +++ b/src/Adafruit_GPS.h @@ -34,7 +34,7 @@ #define NMEA_EXTENSIONS ///< if defined will include more NMEA sentences #endif #else -#if(NMEA_EXTRAS > 0) +#if (NMEA_EXTRAS > 0) #define NMEA_EXTENSIONS ///< if defined will include more NMEA sentences #endif #endif @@ -247,8 +247,8 @@ private: bool isEmpty(char *pStart); // used by check() for validity tests, room for future expansion - const char *sources[6] = {"II", "WI", "GP", - "GN", "P", "ZZZ"}; ///< valid source ids + const char *sources[6] = {"II", "WI", "GP", + "GN", "P", "ZZZ"}; ///< valid source ids #ifdef NMEA_EXTENSIONS const char *sentences_parsed[20] = @@ -260,9 +260,8 @@ private: "APB", "DPT", "GSV", "HDG", "MWD", "ROT", "RPM", "RSA", "VDR", "VTG", "ZDA", "ZZZ"}; ///< known, but not parseable #else // make the lists short to save memory - const char - *sentences_parsed[5] = {"GGA", "GLL", "GSA", "RMC", - "ZZZ"}; ///< parseable sentence ids + const char *sentences_parsed[5] = {"GGA", "GLL", "GSA", "RMC", + "ZZZ"}; ///< parseable sentence ids const char *sentences_known[4] = {"DBT", "HDM", "HDT", "ZZZ"}; ///< known, but not parseable #endif diff --git a/src/NMEA_data.cpp b/src/NMEA_data.cpp index 6a76200..d3d05ed 100644 --- a/src/NMEA_data.cpp +++ b/src/NMEA_data.cpp @@ -58,7 +58,7 @@ void Adafruit_GPS::newDataValue(nmea_index_t idx, nmea_float_t v) { newDataValue((nmea_index_t)(idx + 2), cos(v / RAD_TO_DEG)); } // weighting factor for smoothing depends on delta t / tau - nmea_float_t w = + nmea_float_t w = min((nmea_float_t)1.0, ((nmea_float_t)millis() - val[idx].lastUpdate) / val[idx].response); // default smoothing @@ -438,7 +438,7 @@ void Adafruit_GPS::removeHistory(nmea_index_t idx) { /**************************************************************************/ void Adafruit_GPS::showDataValue(nmea_index_t idx, int n) { Serial.print("idx: "); - if(idx < 10) + if (idx < 10) Serial.print(" "); Serial.print(idx); Serial.print(", "); @@ -461,8 +461,8 @@ void Adafruit_GPS::showDataValue(nmea_index_t idx, int n) { 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 + i >= max(val[idx].hist->n - n, (unsigned)0); + i--) { // most recent first Serial.print(", "); Serial.print(val[idx].hist->data[i]); } diff --git a/src/NMEA_parse.cpp b/src/NMEA_parse.cpp index 36e2621..61d572a 100644 --- a/src/NMEA_parse.cpp +++ b/src/NMEA_parse.cpp @@ -700,9 +700,9 @@ bool Adafruit_GPS::parseCoord(char *pStart, nmea_float_t *angleDegrees, // reject angles that are out of range if (nsew == 'N' || nsew == 'S') - if(abs(deg) > 90) + if (abs(deg) > 90) return false; - if(abs(deg) > 180) + if (abs(deg) > 180) return false; // store in locations passed as args From ebb09d04809875379eba9e4351a051bece88f256 Mon Sep 17 00:00:00 2001 From: Rick Sellens Date: Wed, 29 Jan 2020 13:23:02 -0500 Subject: [PATCH 13/13] clang --- src/NMEA_parse.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/NMEA_parse.cpp b/src/NMEA_parse.cpp index 61d572a..822fc57 100644 --- a/src/NMEA_parse.cpp +++ b/src/NMEA_parse.cpp @@ -700,9 +700,9 @@ bool Adafruit_GPS::parseCoord(char *pStart, nmea_float_t *angleDegrees, // reject angles that are out of range if (nsew == 'N' || nsew == 'S') - if (abs(deg) > 90) + if (abs(deg) > 90) return false; - if (abs(deg) > 180) + if (abs(deg) > 180) return false; // store in locations passed as args