initial commit

This commit is contained in:
2026-09-25 10:48:51 +10:00
commit b90beea8ae
1020 changed files with 131665 additions and 0 deletions
@@ -0,0 +1,621 @@
/**************************************************************************/
/*!
@file Adafruit_GPS.cpp
@mainpage Adafruit Ultimate GPS Breakout
@section intro Introduction
This is the Adafruit GPS library - the ultimate GPS library
for the ultimate GPS module!
Tested and works great with the Adafruit Ultimate GPS module
using MTK33x9 chipset
------> http://www.adafruit.com/products/746
Adafruit invests time and resources providing this open source code,
please support Adafruit and open-source hardware by purchasing
products from Adafruit!
@section author Author
Written by Limor Fried/Ladyada for Adafruit Industries.
@section license License
BSD license, check license.txt for more information
All text above must be included in any redistribution
*/
/**************************************************************************/
#include <Adafruit_GPS.h>
static bool strStartsWith(const char *str, const char *prefix);
/**************************************************************************/
/*!
@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(ARDUINO_UNOR4_WIFI) || defined(ESP8266)) && \
!defined(NO_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 Constructor when using SoftwareSerial
@param ser Pointer to SoftwareSerial device
*/
/**************************************************************************/
#if (defined(__AVR__) || ((defined(ARDUINO_UNOR4_WIFI) || defined(ESP8266)) && \
!defined(NO_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 Stream
@param data Pointer to a Stream object
*/
/**************************************************************************/
Adafruit_GPS::Adafruit_GPS(Stream *data) {
common_init(); // Set everything to common state, then...
gpsStream = data; // ...override gpsStream 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(ARDUINO_UNOR4_WIFI) || defined(ESP8266)) && \
!defined(NO_SW_SERIAL)))
gpsSwSerial = NULL; // Set both to NULL, then override correct
#endif
gpsHwSerial = NULL; // port pointer in corresponding constructor
gpsStream = 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 = antenna = 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 How many bytes are available to read - part of 'Print'-class
functionality
@return Bytes available, 0 if none
*/
/**************************************************************************/
size_t Adafruit_GPS::available(void) {
if (paused)
return 0;
#if (defined(__AVR__) || ((defined(ARDUINO_UNOR4_WIFI) || defined(ESP8266)) && \
!defined(NO_SW_SERIAL)))
if (gpsSwSerial) {
return gpsSwSerial->available();
}
#endif
if (gpsHwSerial) {
return gpsHwSerial->available();
}
if (gpsStream) {
return gpsStream->available();
}
if (gpsI2C || gpsSPI) {
return 1; // I2C/SPI doesnt have 'availability' so always has a byte at
// least to read!
}
return 0;
}
/**************************************************************************/
/*!
@brief Write a byte to the underlying transport - part of 'Print'-class
functionality
@param c A single byte to send
@return Bytes written - 1 on success, 0 on failure
*/
/**************************************************************************/
size_t Adafruit_GPS::write(uint8_t c) {
#if (defined(__AVR__) || ((defined(ARDUINO_UNOR4_WIFI) || defined(ESP8266)) && \
!defined(NO_SW_SERIAL)))
if (gpsSwSerial) {
return gpsSwSerial->write(c);
}
#endif
if (gpsHwSerial) {
return gpsHwSerial->write(c);
}
if (gpsStream) {
return gpsStream->write(c);
}
if (gpsI2C) {
gpsI2C->beginTransmission(_i2caddr);
if (gpsI2C->write(c) != 1) {
return 0;
}
if (gpsI2C->endTransmission(true) == 0) {
return 1;
}
}
if (gpsSPI) {
gpsSPI->beginTransaction(gpsSPI_settings);
if (gpsSPI_cs >= 0) {
digitalWrite(gpsSPI_cs, LOW);
}
c = gpsSPI->transfer(c);
if (gpsSPI_cs >= 0) {
digitalWrite(gpsSPI_cs, HIGH);
}
gpsSPI->endTransaction();
return 1;
}
return 0;
}
/**************************************************************************/
/*!
@brief Read one character from the GPS device.
Call very frequently and multiple times per opportunity or the buffer
may overflow if there are frequent NMEA sentences. An 82 character NMEA
sentence 10 times per second will require 820 calls per second, and
once a loop() may not be enough. Check for newNMEAreceived() after at
least every 10 calls, or you may miss some short sentences.
@return The character that we received, or 0 if nothing was available
*/
/**************************************************************************/
char Adafruit_GPS::read(void) {
static uint32_t firstChar = 0; // first character received in current sentence
uint32_t tStart = millis(); // as close as we can get to time char was sent
char c = 0;
if (paused || noComms)
return c;
#if (defined(__AVR__) || ((defined(ARDUINO_UNOR4_WIFI) || defined(ESP8266)) && \
!defined(NO_SW_SERIAL)))
if (gpsSwSerial) {
if (!gpsSwSerial->available())
return c;
c = gpsSwSerial->read();
}
#endif
if (gpsHwSerial) {
if (!gpsHwSerial->available())
return c;
c = gpsHwSerial->read();
}
if (gpsStream) {
if (!gpsStream->available())
return c;
c = gpsStream->read();
}
if (gpsI2C) {
if (_buff_idx <= _buff_max) {
c = _i2cbuffer[_buff_idx];
_buff_idx++;
} else {
// refill the buffer!
if (gpsI2C->requestFrom((uint8_t)0x10, (uint8_t)GPS_MAX_I2C_TRANSFER,
(uint8_t) true) == GPS_MAX_I2C_TRANSFER) {
// got data!
_buff_max = 0;
char curr_char = 0;
for (int i = 0; i < GPS_MAX_I2C_TRANSFER; i++) {
curr_char = gpsI2C->read();
if ((curr_char == 0x0A) && (last_char != 0x0D)) {
// skip duplicate 0x0A's - but keep as part of a CRLF
continue;
}
last_char = curr_char;
_i2cbuffer[_buff_max] = curr_char;
_buff_max++;
}
_buff_max--; // back up to the last valid slot
if ((_buff_max == 0) && (_i2cbuffer[0] == 0x0A)) {
_buff_max = -1; // ahh there was nothing to read after all
}
_buff_idx = 0;
}
return c;
}
}
if (gpsSPI) {
do {
gpsSPI->beginTransaction(gpsSPI_settings);
if (gpsSPI_cs >= 0) {
digitalWrite(gpsSPI_cs, LOW);
}
c = gpsSPI->transfer(0xFF);
if (gpsSPI_cs >= 0) {
digitalWrite(gpsSPI_cs, HIGH);
}
gpsSPI->endTransaction();
// skip duplicate 0x0A's - but keep as part of a CRLF
} while (((c == 0x0A) && (last_char != 0x0D)) ||
(!isprint(c) && !isspace(c)));
last_char = c;
}
// Serial.print(c);
currentline[lineidx] = c;
lineidx = lineidx + 1;
if (lineidx >= MAXLINELENGTH)
lineidx = MAXLINELENGTH -
1; // ensure there is someplace to put the next received character
if (c == '\n') {
currentline[lineidx] = 0;
if (currentline == line1) {
currentline = line2;
lastline = line1;
} else {
currentline = line1;
lastline = line2;
}
// Serial.println("----");
// Serial.println((char *)lastline);
// Serial.println("----");
lineidx = 0;
recvdflag = true;
recvdTime = millis(); // time we got the end of the string
sentTime = firstChar;
firstChar = 0; // there are no characters yet
return c; // wait until next character to set time
}
if (firstChar == 0)
firstChar = tStart;
return c;
}
/**************************************************************************/
/*!
@brief Send a command to the GPS device
@param str Pointer to a string holding the command to send
*/
/**************************************************************************/
void Adafruit_GPS::sendCommand(const char *str) { println(str); }
/**************************************************************************/
/*!
@brief Check to see if a new NMEA line has been received
@return True if received, false if not
*/
/**************************************************************************/
bool Adafruit_GPS::newNMEAreceived(void) { return recvdflag; }
/**************************************************************************/
/*!
@brief Pause/unpause receiving new data
@param p True = pause, false = unpause
*/
/**************************************************************************/
void Adafruit_GPS::pause(bool p) { paused = p; }
/**************************************************************************/
/*!
@brief Returns the last NMEA line received and unsets the received flag
@return Pointer to the last line string
*/
/**************************************************************************/
char *Adafruit_GPS::lastNMEA(void) {
recvdflag = false;
return (char *)lastline;
}
/**************************************************************************/
/*!
@brief Wait for a specified sentence from the device
@param wait4me Pointer to a string holding the desired response
@param max How long to wait, default is MAXWAITSENTENCE
@param usingInterrupts True if using interrupts to read from the GPS
(default is false)
@return True if we got what we wanted, false otherwise
*/
/**************************************************************************/
bool Adafruit_GPS::waitForSentence(const char *wait4me, uint8_t max,
bool usingInterrupts) {
uint8_t i = 0;
while (i < max) {
if (!usingInterrupts)
read();
if (newNMEAreceived()) {
char *nmea = lastNMEA();
i++;
if (strStartsWith(nmea, wait4me))
return true;
}
}
return false;
}
/**************************************************************************/
/*!
@brief Start the LOCUS logger
@return True on success, false if it failed
*/
/**************************************************************************/
bool Adafruit_GPS::LOCUS_StartLogger(void) {
sendCommand(PMTK_LOCUS_STARTLOG);
recvdflag = false;
return waitForSentence(PMTK_LOCUS_STARTSTOPACK);
}
/**************************************************************************/
/*!
@brief Stop the LOCUS logger
@return True on success, false if it failed
*/
/**************************************************************************/
bool Adafruit_GPS::LOCUS_StopLogger(void) {
sendCommand(PMTK_LOCUS_STOPLOG);
recvdflag = false;
return waitForSentence(PMTK_LOCUS_STARTSTOPACK);
}
/**************************************************************************/
/*!
@brief Read the logger status
@return True if we read the data, false if there was no response
*/
/**************************************************************************/
bool Adafruit_GPS::LOCUS_ReadStatus(void) {
sendCommand(PMTK_LOCUS_QUERY_STATUS);
if (!waitForSentence("$PMTKLOG"))
return false;
char *response = lastNMEA();
uint16_t parsed[10];
uint8_t i;
for (i = 0; i < 10; i++)
parsed[i] = -1;
response = strchr(response, ',');
for (i = 0; i < 10; i++) {
if (!response || (response[0] == 0) || (response[0] == '*'))
break;
response++;
parsed[i] = 0;
while ((response[0] != ',') && (response[0] != '*') && (response[0] != 0)) {
parsed[i] *= 10;
char c = response[0];
if (isDigit(c))
parsed[i] += c - '0';
else
parsed[i] = c;
response++;
}
}
LOCUS_serial = parsed[0];
LOCUS_type = parsed[1];
if (isAlpha(parsed[2])) {
parsed[2] = parsed[2] - 'a' + 10;
}
LOCUS_mode = parsed[2];
LOCUS_config = parsed[3];
LOCUS_interval = parsed[4];
LOCUS_distance = parsed[5];
LOCUS_speed = parsed[6];
LOCUS_status = !parsed[7];
LOCUS_records = parsed[8];
LOCUS_percent = parsed[9];
return true;
}
/**************************************************************************/
/*!
@brief Standby Mode Switches
@return False if already in standby, true if it entered standby
*/
/**************************************************************************/
bool Adafruit_GPS::standby(void) {
if (inStandbyMode) {
return false; // Returns false if already in standby mode, so that you do
// not wake it up by sending commands to GPS
} else {
inStandbyMode = true;
sendCommand(PMTK_STANDBY);
// return waitForSentence(PMTK_STANDBY_SUCCESS); // don't seem to be fast
// enough to catch the message, or something else just is not working
return true;
}
}
/**************************************************************************/
/*!
@brief Wake the sensor up
@return True if woken up, false if not in standby or failed to wake
*/
/**************************************************************************/
bool Adafruit_GPS::wakeup(void) {
if (inStandbyMode) {
inStandbyMode = false;
sendCommand(""); // send byte to wake it up
return waitForSentence(PMTK_AWAKE);
} else {
return false; // Returns false if not in standby mode, nothing to wakeup
}
}
/**************************************************************************/
/*!
@brief Time in seconds since the last position fix was obtained. The
time returned is limited to 2^32 milliseconds, which is about 49.7 days.
It will wrap around to zero if no position fix is received
for this long.
@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. The time
returned is limited to 2^32 milliseconds, which is about 49.7 days. It
will wrap around to zero if no GPS time is received for this long.
@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. The time
returned is limited to 2^32 milliseconds, which is about 49.7 days. It
will wrap around to zero if no GPS date is received for this long.
@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
@param str Pointer to a string
@param prefix Pointer to the prefix
@return True if str starts with prefix, false otherwise
*/
/**************************************************************************/
static bool strStartsWith(const char *str, const char *prefix) {
while (*prefix) {
if (*prefix++ != *str++)
return false;
}
return true;
}
@@ -0,0 +1,317 @@
/**************************************************************************/
/*!
@file Adafruit_GPS.h
This is the Adafruit GPS library - the ultimate GPS library
for the ultimate GPS module!
Tested and works great with the Adafruit Ultimate GPS module
using MTK33x9 chipset
------> http://www.adafruit.com/products/746
Pick one up today at the Adafruit electronics shop
and help support open source hardware & software! -ada
Adafruit invests time and resources providing this open source code,
please support Adafruit and open-source hardware by purchasing
products from Adafruit!
Written by Limor Fried/Ladyada for Adafruit Industries.
BSD license, check license.txt for more information
All text above must be included in any redistribution
*/
/**************************************************************************/
#ifndef _ADAFRUIT_GPS_H
#define _ADAFRUIT_GPS_H
/**************************************************************************/
/**
Comment out the definition of NMEA_EXTENSIONS to make the library use as
little memory as possible for GPS functionality only. The ARDUINO_ARCH_AVR
test should leave it out of any compilations for the UNO and similar. */
#ifndef NMEA_EXTRAS // inject on the compile command line to force extensions
#ifndef ARDUINO_ARCH_AVR
#define NMEA_EXTENSIONS ///< if defined will include more NMEA sentences
#endif
#else
#if (NMEA_EXTRAS > 0)
#define NMEA_EXTENSIONS ///< if defined will include more NMEA sentences
#endif
#endif
#if (defined(__AVR__) || ((defined(ARDUINO_UNOR4_WIFI) || defined(ESP8266)) && \
!defined(NO_SW_SERIAL)))
#define USE_SW_SERIAL ///< insert line `#define NO_SW_SERIAL` before this header
///< if you don't want to include software serial in the
#endif ///< library
#define GPS_DEFAULT_I2C_ADDR \
0x10 ///< The default address for I2C transport of GPS data
#define GPS_MAX_I2C_TRANSFER \
32 ///< The max number of bytes we'll try to read at once
#define GPS_MAX_SPI_TRANSFER \
100 ///< The max number of bytes we'll try to read at once
#define MAXLINELENGTH 120 ///< how long are max NMEA lines to parse?
#define NMEA_MAX_SENTENCE_ID \
20 ///< maximum length of a sentence ID name, including terminating 0
#define NMEA_MAX_SOURCE_ID \
3 ///< maximum length of a source ID name, including terminating 0
#include "Arduino.h"
#ifdef USE_SW_SERIAL
#include <SoftwareSerial.h>
#endif
#include <Adafruit_PMTK.h>
#include <NMEA_data.h>
#include <SPI.h>
#include <Wire.h>
/// 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_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
NMEA_HAS_SENTENCE = 20, ///< has a recognized sentence ID
NMEA_HAS_SENTENCE_P = 40 ///< has a recognized parseable sentence ID
} nmea_check_t;
/**************************************************************************/
/*!
@brief The GPS class
*/
class Adafruit_GPS : public Print {
public:
// Adafruit_GPS.cpp
bool begin(uint32_t baud_or_i2caddr);
#ifdef USE_SW_SERIAL
Adafruit_GPS(SoftwareSerial *ser); // Constructor when using SoftwareSerial
#endif
Adafruit_GPS(HardwareSerial *ser); // Constructor when using HardwareSerial
Adafruit_GPS(Stream *data); // Constructor when using Stream
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();
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();
// NMEA_parse.cpp
bool parse(char *);
bool check(char *nmea);
bool onList(char *nmea, const char **list);
uint8_t parseHex(char c);
// NMEA_build.cpp
#ifdef NMEA_EXTENSIONS
char *build(char *nmea, const char *thisSource, const char *thisSentence,
char ref = 'R', bool noCRLF = false);
#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
char thisSentence[NMEA_MAX_SENTENCE_ID] = {
0}; ///< the next three letters of the current sentence, e.g. GLL, RMC
char lastSource[NMEA_MAX_SOURCE_ID] = {
0}; ///< the results of the check on the most recent successfully parsed
///< sentence
char lastSentence[NMEA_MAX_SENTENCE_ID] = {
0}; ///< the next three letters of the most recent successfully parsed
///< sentence, e.g. GLL, RMC
uint8_t hour; ///< GMT hours
uint8_t minute; ///< GMT minutes
uint8_t seconds; ///< GMT seconds
uint16_t milliseconds; ///< GMT milliseconds
uint8_t year; ///< GMT year
uint8_t month; ///< GMT month
uint8_t day; ///< GMT day
nmea_float_t latitude; ///< Floating point latitude value in degrees/minutes
///< as received from the GPS (DDMM.MMMM)
nmea_float_t longitude; ///< Floating point longitude value in degrees/minutes
///< as received from the GPS (DDDMM.MMMM)
/** Fixed point latitude and longitude value with degrees stored in units of
1/10000000 of a degree. See pull #13 for more details:
https://github.com/adafruit/Adafruit-GPS-Library/pull/13 */
int32_t latitude_fixed; ///< Fixed point latitude in decimal degrees.
///< Divide by 10000000.0 to get a double.
int32_t longitude_fixed; ///< Fixed point longitude in decimal degrees
///< Divide by 10000000.0 to get a double.
nmea_float_t latitudeDegrees; ///< Latitude in decimal degrees
nmea_float_t longitudeDegrees; ///< Longitude in decimal degrees
nmea_float_t geoidheight; ///< Diff between geoid height and WGS84 height
nmea_float_t altitude; ///< Altitude in meters above MSL
nmea_float_t speed; ///< Current speed over ground in knots
nmea_float_t angle; ///< Course in degrees from true north
nmea_float_t magvariation; ///< Magnetic variation in degrees (vs. true north)
nmea_float_t HDOP; ///< Horizontal Dilution of Precision - relative accuracy
///< of horizontal position
nmea_float_t VDOP; ///< Vertical Dilution of Precision - relative accuracy
///< of vertical position
nmea_float_t PDOP; ///< Position Dilution of Precision - Complex maths derives
///< a simple, single number for each kind of DOP
char lat = 'X'; ///< N/S
char lon = 'X'; ///< E/W
char mag = 'X'; ///< Magnetic variation direction
bool fix; ///< Have a fix?
uint8_t fixquality; ///< Fix quality (0, 1, 2 = Invalid, GPS, DGPS)
uint8_t fixquality_3d; ///< 3D fix quality (1, 3, 3 = Nofix, 2D fix, 3D fix)
uint8_t satellites; ///< Number of satellites in use
uint8_t antenna; ///< Antenna that is used (from PGTOP)
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
uint8_t LOCUS_mode; ///< Logging mode, 0x08 interval logger
uint8_t LOCUS_config; ///< Contents of configuration
uint8_t LOCUS_interval; ///< Interval setting
uint8_t LOCUS_distance; ///< Distance setting
uint8_t LOCUS_speed; ///< Speed setting
uint8_t LOCUS_status; ///< 0: Logging, 1: Stop logging
uint8_t LOCUS_percent; ///< Log life used percentage
#ifdef NMEA_EXTENSIONS
// 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
int txtN = 0; ///< the TXT sentence number
#endif // NMEA_EXTENSIONS
private:
// void parseLat(char *);
// bool parseLatDir(char *);
// void parseLon(char *);
// bool parseLonDir(char *);
// NMEA_data.cpp
void data_init();
// NMEA_parse.cpp
const char *tokenOnList(char *token, const char **list);
bool parseCoord(char *p, nmea_float_t *angleDegrees = NULL,
nmea_float_t *angle = NULL, int32_t *angle_fixed = NULL,
char *dir = NULL);
char *parseStr(char *buff, char *p, int n);
bool parseTime(char *);
bool parseFix(char *);
bool parseAntenna(char *);
bool isEmpty(char *pStart);
// used by check() for validity tests, room for future expansion
const char *sources[7] = {"II", "WI", "GP", "PG",
"GN", "P", "ZZZ"}; ///< valid source ids
#ifdef NMEA_EXTENSIONS
const char *sentences_parsed[21] = {"GGA", "GLL", "GSA", "RMC", "DBT", "HDM",
"HDT", "MDA", "MTW", "MWV", "RMB", "TOP",
"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[6] = {"GGA", "GLL", "GSA", "RMC",
"TOP", "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.
uint32_t lastUpdate =
2000000000L; ///< millis() when last full sentence successfully parsed
uint32_t lastFix = 2000000000L; ///< millis() when last fix received
uint32_t lastTime = 2000000000L; ///< millis() when last time received
uint32_t lastDate = 2000000000L; ///< millis() when last date received
uint32_t recvdTime =
2000000000L; ///< millis() when last full sentence received
uint32_t sentTime = 2000000000L; ///< millis() when first character of last
///< full sentence received
bool paused;
uint8_t parseResponse(char *response);
#ifdef USE_SW_SERIAL
SoftwareSerial *gpsSwSerial;
#endif
bool noComms = false;
HardwareSerial *gpsHwSerial;
Stream *gpsStream;
TwoWire *gpsI2C;
SPIClass *gpsSPI;
int8_t gpsSPI_cs = -1;
SPISettings gpsSPI_settings =
SPISettings(1000000, MSBFIRST, SPI_MODE0); // default
char _spibuffer[GPS_MAX_SPI_TRANSFER]; // for when we write data, we need to
// read it too!
uint8_t _i2caddr;
char _i2cbuffer[GPS_MAX_I2C_TRANSFER];
int8_t _buff_max = -1, _buff_idx = 0;
char last_char = 0;
volatile char line1[MAXLINELENGTH]; ///< We double buffer: read one line in
///< and leave one for the main program
volatile char line2[MAXLINELENGTH]; ///< Second buffer
volatile uint8_t lineidx = 0; ///< our index into filling the current line
volatile char *currentline; ///< Pointer to current line buffer
volatile char *lastline; ///< Pointer to previous line buffer
volatile bool recvdflag; ///< Received flag
volatile bool inStandbyMode; ///< In standby flag
};
/**************************************************************************/
#endif
@@ -0,0 +1,116 @@
/**************************************************************************/
/*!
@file Adafruit_PMTK.h
This is the Adafruit GPS library - the ultimate GPS library
for the ultimate GPS module!
Tested and works great with the Adafruit Ultimate GPS module
using MTK33x9 chipset
------> http://www.adafruit.com/products/746
Pick one up today at the Adafruit electronics shop
and help support open source hardware & software! -ada
Adafruit invests time and resources providing this open source code,
please support Adafruit and open-source hardware by purchasing
products from Adafruit!
Written by Limor Fried/Ladyada for Adafruit Industries.
BSD license, check license.txt for more information
All text above must be included in any redistribution
*/
/**************************************************************************/
#ifndef _ADAFRUIT_PMTK_H
#define _ADAFRUIT_PMTK_H
/**************************************************************************/
/**
Different commands to set the update rate from once a second (1 Hz) to 10 times
a second (10Hz) Note that these only control the rate at which the position is
echoed, to actually speed up the position fix you must also send one of the
position fix rate commands below too. */
#define PMTK_SET_NMEA_UPDATE_100_MILLIHERTZ \
"$PMTK220,10000*2F" ///< Once every 10 seconds, 100 millihertz.
#define PMTK_SET_NMEA_UPDATE_200_MILLIHERTZ \
"$PMTK220,5000*1B" ///< Once every 5 seconds, 200 millihertz.
#define PMTK_SET_NMEA_UPDATE_1HZ "$PMTK220,1000*1F" ///< 1 Hz
#define PMTK_SET_NMEA_UPDATE_2HZ "$PMTK220,500*2B" ///< 2 Hz
#define PMTK_SET_NMEA_UPDATE_5HZ "$PMTK220,200*2C" ///< 5 Hz
#define PMTK_SET_NMEA_UPDATE_10HZ "$PMTK220,100*2F" ///< 10 Hz
// Position fix update rate commands.
#define PMTK_API_SET_FIX_CTL_100_MILLIHERTZ \
"$PMTK300,10000,0,0,0,0*2C" ///< Once every 10 seconds, 100 millihertz.
#define PMTK_API_SET_FIX_CTL_200_MILLIHERTZ \
"$PMTK300,5000,0,0,0,0*18" ///< Once every 5 seconds, 200 millihertz.
#define PMTK_API_SET_FIX_CTL_1HZ "$PMTK300,1000,0,0,0,0*1C" ///< 1 Hz
#define PMTK_API_SET_FIX_CTL_5HZ "$PMTK300,200,0,0,0,0*2F" ///< 5 Hz
// Can't fix position faster than 5 times a second!
#define PMTK_SET_BAUD_115200 "$PMTK251,115200*1F" ///< 115200 bps
#define PMTK_SET_BAUD_57600 "$PMTK251,57600*2C" ///< 57600 bps
#define PMTK_SET_BAUD_9600 "$PMTK251,9600*17" ///< 9600 bps
#define PMTK_SET_NMEA_OUTPUT_GLLONLY \
"$PMTK314,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0*29" ///< turn on only the
///< GPGLL sentence
#define PMTK_SET_NMEA_OUTPUT_RMCONLY \
"$PMTK314,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0*29" ///< turn on only the
///< GPRMC sentence
#define PMTK_SET_NMEA_OUTPUT_VTGONLY \
"$PMTK314,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0*29" ///< turn on only the
///< GPVTG
#define PMTK_SET_NMEA_OUTPUT_GGAONLY \
"$PMTK314,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0*29" ///< turn on just the
///< GPGGA
#define PMTK_SET_NMEA_OUTPUT_GSAONLY \
"$PMTK314,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0*29" ///< turn on just the
///< GPGSA
#define PMTK_SET_NMEA_OUTPUT_GSVONLY \
"$PMTK314,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0*29" ///< turn on just the
///< GPGSV
#define PMTK_SET_NMEA_OUTPUT_RMCGGA \
"$PMTK314,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0*28" ///< turn on GPRMC and
///< GPGGA
#define PMTK_SET_NMEA_OUTPUT_RMCGGAGSA \
"$PMTK314,0,1,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0*29" ///< turn on GPRMC, GPGGA
///< and GPGSA
#define PMTK_SET_NMEA_OUTPUT_ALLDATA \
"$PMTK314,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0*28" ///< turn on ALL THE DATA
#define PMTK_SET_NMEA_OUTPUT_OFF \
"$PMTK314,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0*28" ///< turn off output
// to generate your own sentences, check out the MTK command datasheet and use a
// checksum calculator such as the awesome
// http://www.hhhh.org/wiml/proj/nmeaxor.html
#define PMTK_LOCUS_STARTLOG "$PMTK185,0*22" ///< Start logging data
#define PMTK_LOCUS_STOPLOG "$PMTK185,1*23" ///< Stop logging data
#define PMTK_LOCUS_STARTSTOPACK \
"$PMTK001,185,3*3C" ///< Acknowledge the start or stop command
#define PMTK_LOCUS_QUERY_STATUS "$PMTK183*38" ///< Query the logging status
#define PMTK_LOCUS_ERASE_FLASH "$PMTK184,1*22" ///< Erase the log flash data
#define LOCUS_OVERLAP \
0 ///< If flash is full, log will overwrite old data with new logs
#define LOCUS_FULLSTOP 1 ///< If flash is full, logging will stop
#define PMTK_ENABLE_SBAS \
"$PMTK313,1*2E" ///< Enable search for SBAS satellite (only works with 1Hz
///< output rate)
#define PMTK_ENABLE_WAAS "$PMTK301,2*2E" ///< Use WAAS for DGPS correction data
#define PMTK_STANDBY \
"$PMTK161,0*28" ///< standby command & boot successful message
#define PMTK_STANDBY_SUCCESS "$PMTK001,161,3*36" ///< Not needed currently
#define PMTK_AWAKE "$PMTK010,002*2D" ///< Wake up
#define PMTK_Q_RELEASE "$PMTK605*31" ///< ask for the release and version
#define PGCMD_ANTENNA \
"$PGCMD,33,1*6C" ///< request for updates on antenna status
#define PGCMD_NOANTENNA "$PGCMD,33,0*6D" ///< don't show antenna status messages
#define MAXWAITSENTENCE \
10 ///< how long to wait when we're looking for a response
/**************************************************************************/
#endif
@@ -0,0 +1,619 @@
/**************************************************************************/
/*!
@file NMEA_build.cpp
This is the Adafruit GPS library - the ultimate GPS library
for the ultimate GPS module!
Tested and works great with the Adafruit Ultimate GPS module
using MTK33x9 chipset
------> http://www.adafruit.com/products/746
Pick one up today at the Adafruit electronics shop
and help support open source hardware & software! -ada
Adafruit invests time and resources providing this open source code,
please support Adafruit and open-source hardware by purchasing
products from Adafruit!
@author Limor Fried/Ladyada for Adafruit Industries.
@copyright BSD license, check license.txt for more information
All text above must be included in any redistribution
*/
/**************************************************************************/
#include <Adafruit_GPS.h>
#ifdef NMEA_EXTENSIONS
/**************************************************************************/
/*!
@brief Build an NMEA sentence string based on the relevant variables.
Sentences start with a $, then a two character source identifier, then
a three character sentence name 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. Most of these sentence
definitions were found at http://fort21.ru/download/NMEAdescription.pdf
build() will work with other lengths for source and sentence to allow
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
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
to be valid, so these sentences may contain values that are stale, or
the result of initialization rather than measurement.
@param nmea Pointer to the NMEA string buffer. Must be big enough to
hold the sentence. No guarantee what will be in it if the
building of the sentence fails.
@param thisSource Pointer to the source name string (2 upper case)
@param thisSentence Pointer to the sentence name string (3 upper case)
@param ref Reference for the sentence, usually relative (R) or true (T)
@param noCRLF set true to disable adding CR/LF to comply with NMEA-183
@return Pointer to sentence if successful, NULL if fails
*/
/**************************************************************************/
char *Adafruit_GPS::build(char *nmea, const char *thisSource,
const char *thisSentence, char ref, bool noCRLF) {
sprintf(nmea, "%6.2f",
(double)123.45); // fail if sprintf() doesn't handle floats
if (strcmp(nmea, "123.45"))
return NULL;
*nmea = '$';
char *p = nmea + 1; // Pointer to move through the sentence
strncpy(p, thisSource, strlen(thisSource));
p += strlen(thisSource);
strncpy(p, thisSentence, strlen(thisSentence));
p += strlen(thisSentence);
*p = ',';
p += 1; // Now $XXSSS, and need to add argument fields
// 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
// 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
// | | | | | | | | | | | | | | |
//$--GGA,hhmmss.ss,ddmm.mm,a,dddmm.mm,a,x,xx,x.x,x.x,M,x.x,M,x.x,xxxx*hh
// 1) Time (UTC)
// 2) Latitude
// 3) N or S (North or South)
// 4) Longitude
// 5) E or W (East or West)
// 6) GPS Quality Indicator, 0 - fix not available, 1 - GPS fix, 2 -
// Differential GPS fix 7) Number of satellites in view, 00 - 12 8)
// Horizontal Dilution of precision 9) Antenna Altitude above/below
// mean-sea-level (geoid) 10) Units of antenna altitude, meters 11) Geoidal
// separation, the difference between the WGS-84 earth
// ellipsoid and mean-sea-level (geoid), "-" means mean-sea-level below
// ellipsoid
// 12) Units of geoidal separation, meters
// 13) Age of differential GPS data, time in seconds since last SC104
// type 1 or 9 update, null field when DGPS is not used
// 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);
} else if (!strcmp(thisSentence, "GLL")) { //*****************************GLL
// GLL Geographic Position – Latitude/Longitude
// 1 2 3 4 5 6 7
// | | | | | | |
//$--GLL,llll.ll,a,yyyyy.yy,a,hhmmss.ss,A*hh
// 1) Latitude ddmm.mm format
// 2) N or S (North or South)
// 3) Longitude dddmm.mm format
// 4) E or W (East or West)
// 5) Time (UTC)
// 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.);
} else if (!strcmp(thisSentence, "GSA")) { //*****************************GSA
// GSA GPS DOP and active satellites
// 1 2 3 14 15 16 17 18
// | | | | | | | |
//$--GSA,a,a,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x.x,x.x,x.x*hh
// 1) Selection mode
// 2) Mode
// 3) ID of 1st satellite used for fix
// 4) ID of 2nd satellite used for fix
// ...
// 14) ID of 12th satellite used for fix
// 15) PDOP in meters
// 16) HDOP in meters
// 17) VDOP in meters
// 18) Checksum
return NULL;
} else if (!strcmp(thisSentence, "RMC")) { //*****************************RMC
// RMC Recommended Minimum Navigation Information
// 12
// 1 2 3 4 5 6 7 8 9 10 11 |
// | | | | | | | | | | | |
//$--RMC,hhmmss.ss,A,llll.ll,a,yyyyy.yy,a,x.x,x.x,xxxxxx,x.x,a*hh
// 1) Time (UTC)
// 2) Status, V = Navigation receiver warning
// 3) Latitude
// 4) N or S
// 5) Longitude
// 6) E or W
// 7) Speed over ground, knots
// 8) Track made good, degrees true
// 9) Date, ddmmyy
// 10) Magnetic Variation, degrees
// 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);
} 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
// | | | | |
//$--TXT,xx,xx,xx,c--c*hh
// 1) Total Number of Sentences 01-99
// 2) Sentence Number 01-99
// 3) Text Identifier 01-99
// 4) Text String, max 61 characters
// 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
}
addChecksum(nmea); // Successful completion
if (!noCRLF) { // Add Carriage Return and Line Feed to comply with NMEA-183
size_t len = strlen(nmea);
char *nmeaWithCRLF =
(char *)malloc(len + 3); // +2 for \r\n, +1 for null terminator
if (nmeaWithCRLF) {
strcpy(nmeaWithCRLF, nmea); // Copy original string
strcat(nmeaWithCRLF, "\r\n"); // Append \r\n
strcpy(nmea, nmeaWithCRLF); // Copy back to original buffer
free(nmeaWithCRLF); // Free the allocated memory
}
}
return nmea; // return pointer to finished product
}
#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++;
}
// Calculate the needed buffer size: original length + 3 (*XX) + 1 (null
// terminator)
int neededSize = strlen(buff) + 4;
char *tempBuffer = (char *)malloc(neededSize);
if (tempBuffer != NULL) {
// Use snprintf to safely format the string with the checksum
snprintf(tempBuffer, neededSize, "%s*%02X", buff, cs);
// Copy the formatted string back to the original buffer
// Note: Make sure the original buffer is large enough to hold the new
// string.
strcpy(buff, tempBuffer);
// Free the allocated memory to avoid memory leaks
free(tempBuffer);
}
}
@@ -0,0 +1,566 @@
/**************************************************************************/
/*!
@file NMEA_data.cpp
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.
@author Rick Sellens.
@copyright 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 / (nmea_float_t)RAD_TO_DEG));
newDataValue((nmea_index_t)(idx + 2), cos(v / (nmea_float_t)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.0f - 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. Select scale and offset values carefully so that
operations and results will fit inside 16 bit integer limits. For example
a scale of 1.0 and an offset of 100000.0 would be a good choice for
atmospheric pressure in Pa with values ranging ~ +/- 3500, while a scale
of 10.0 would be pushing the integer limits.
@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.
@param historyN Set size of data buffer.
@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.0f)
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) {
// put the sin angle in -90 to 90 range
nmea_float_t sAng = asin(s) * (nmea_float_t)RAD_TO_DEG;
while (sAng < -90)
sAng += 180.0f;
while (sAng > 90)
sAng -= 180.0f;
// put the cos angle in 0 to 180 range
nmea_float_t cAng = acos(c) * (nmea_float_t)RAD_TO_DEG;
while (cAng < 0)
cAng += 180.0f;
while (cAng > 180)
cAng -= 180.0f;
// 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.0f; // round up
while (ang > 360)
ang -= 360.0f; // round down
}
return ang;
}
#endif // NMEA_EXTENSIONS
@@ -0,0 +1,194 @@
/**************************************************************************/
/*!
@file NMEA_data.h
*/
/**************************************************************************/
#ifndef _NMEA_DATA_H
#define _NMEA_DATA_H
#include "Arduino.h"
#define NMEA_MAX_WP_ID \
20 ///< maximum length of a waypoint ID name, including terminating 0
#define NMEA_MAX_SENTENCE_ID \
20 ///< maximum length of a sentence ID name, including terminating 0
#define NMEA_MAX_SOURCE_ID \
3 ///< maximum length of a source ID name, including terminating 0
/*************************************************************************
doubles and floats are identical on AVR processors like the UNO where space
is tight. doubles avoid the roundoff errors that led to the fixed point mods
in https://github.com/adafruit/Adafruit-GPS-Library/pull/13, provided the
processor supports actual doubles like the SAMD series with more storage. The
total penalty for going all double is under a few hundred bytes / instance or
0 bytes / instance on an UNO. This typedef allows a switch to lower precision
to save some storage if needed. A float carries 23 bits of fractional
resolution, giving a resolution of at least 9 significant digits, thus 6
significant digits in the decimal place of an angular value like latitude, and
thus a resolution on earth of at least 110 mm. That's closer than GPS will
hit, and closer than needed for navigation, so floats can be used to save a
little storage.
**************************************************************************/
#ifndef NMEA_FLOAT_T
#define NMEA_FLOAT_T float ///< let float be overidden on command line
#endif
typedef NMEA_FLOAT_T
nmea_float_t; ///< the type of variables to use for floating point
/**************************************************************************/
/*!
Struct to contain all the details associated with the history of an NMEA
data value as an optional extension to the data value struct. The history
is stored as scaled integers to save space while caputuring a reasonable
level of resolution. The integer is set equal to scale * (X - offset) and
can be converted back to an approximate float value with
X = I / scale + offset
Only some tags have history in order to save memory. Most of the memory
cost is directly in the array.
192 history values taken every 20 seconds covers just over an hour.
**************************************************************************/
typedef struct {
int16_t *data = NULL; ///< array of ints, oldest first
unsigned n = 0; ///< number of history array elements
uint32_t lastHistory = 0; ///< millis() when history was last updated
uint16_t historyInterval = 20; ///< seconds between history updates
nmea_float_t scale = 1.0; ///< history = (smoothed - offset) * scale
nmea_float_t offset = 0.0; ///< value = (float) history / scale + offset
} nmea_history_t;
/**************************************************************************/
/*!
Type to characterize the type of value stored in a data value struct.
The sine and cosine components of some angles allow
for smoothing of those angles by averaging of sine and cosine values
that are continuous, rather than angles that are discontinuous at
-180/180 or 359/0 transitions. Types 10-19 must have three contiguous
data value entries set up in the matrix to accommodate the extra sin
and cos values.
*/
/**************************************************************************/
typedef enum {
NMEA_SIMPLE_FLOAT = 0, ///< A simple floating point number
NMEA_COMPASS_ANGLE = 1, ///< A compass style angle from 0 to 360 degrees
NMEA_BOAT_ANGLE = 2, ///< An angle relative to the boat orientation
///< from -180 (port) to 180 degrees
NMEA_COMPASS_ANGLE_SIN =
11, ///< A compass style angle from 0 to 360 degrees, with sin and cos
///< elements stored for averaging, etc.
NMEA_BOAT_ANGLE_SIN =
12, ///< An angle relative to the boat orientation from -180 (port) to 180
///< degrees, with sin and cos elements stored for averaging, etc.
NMEA_DDMM = 20, ///< A latitude or longitude angle stored in DDMM.mmmm format
///< like it comes in from the GPS
NMEA_HHMMSS =
30 ///< A time stored in HHMMSS format like it comes in from the GPS
} nmea_value_type_t;
/**************************************************************************/
/*!
Struct to contain all the details associated with an NMEA data value that
can be tracked through time to see how it changes, carries a label, units,
and a format string to determine how it is displayed. Memory footprint
of about 32 bytes per data value, so not tenable in small memory spaces.
*/
/**************************************************************************/
typedef struct {
nmea_float_t latest = 0.0; ///< the most recently obtained value
nmea_float_t smoothed =
0.0; ///< smoothed value based on weight of dt/response
uint32_t lastUpdate = 0; ///< millis() when latest was last set
uint16_t response = 1000; ///< time constant in millis for smoothing
nmea_value_type_t type =
NMEA_SIMPLE_FLOAT; ///< type of float data value represented
byte ockam = 0; ///< the corresponding Ockam Instruments tag number, 0-128
nmea_history_t *hist = NULL; ///< pointer to history, if any
char *label = NULL; ///< pointer to quantity label, if any
char *unit = NULL; ///< pointer to units label, if any
char *fmt = NULL; ///< pointer to format string, if any
} nmea_datavalue_t;
/**************************************************************************/
/*!
Type to provide an index into the array of data values for different
NMEA quantities. The sine and cosine components of some angles allow
for smoothing of those angles by averaging of sine and cosine values
that are continuous, rather than angles that are discontinuous at
-180/180 or 359/0 transitions. Note that the enumerations are arranged
so that NMEA_XXX_SIN = NMEA_XXX + 1, and NMEA_XXX_COS = NMEA_XXX + 2.
*/
/**************************************************************************/
typedef enum {
NMEA_HDOP = 0, ///< Horizontal Dilution of Position
NMEA_LAT, ///< Latitude in signed decimal degrees -90 to 90
NMEA_LON, ///< Longitude in signed decimal degrees -180 to 180
NMEA_LATWP, ///< Waypoint Latitude in signed decimal degrees -90 to 90
NMEA_LONWP, ///< Waypoint Longitude in signed decimal degrees -180 to 180
NMEA_SOG, ///< Speed over Ground in knots
NMEA_COG, ///< Course over ground, 0 to 360 degrees true
NMEA_COG_SIN, ///< sine of Course over ground
NMEA_COG_COS, ///< cosine of Course over ground
NMEA_COGWP, ///< Course over ground to the waypoint, 0 to 360 degrees true
NMEA_XTE, ///< Cross track error for the current segment to the waypoint,
///< Nautical Miles -ve to the left
NMEA_DISTWP, ///< Distance to the waypoint in nautical miles
NMEA_AWA, ///< apparent wind angle relative to the boat -180 to 180 degrees
NMEA_AWA_SIN, ///< sine of apparent wind angle relative to the boat
NMEA_AWA_COS, ///< cosine of apparent wind angle relative to the boat
NMEA_AWS, ///< apparent wind speed, will be coerced to knots
NMEA_TWA, ///< true wind angle relative to the boat -180 to 180 degrees
NMEA_TWA_SIN, ///< sine of true wind angle relative to the boat
NMEA_TWA_COS, ///< cosine of true wind angle relative to the boat
NMEA_TWD, ///< true wind compass direction, magnetic 0 to 360 degrees magnetic
NMEA_TWD_SIN, ///< sine of true wind compass direction, magnetic
NMEA_TWD_COS, ///< cosine of true wind compass direction, magnetic
NMEA_TWS, ///< true wind speed in knots TWS
NMEA_VMG, ///< velocity made good relative to the wind -ve means downwind,
///< knots
NMEA_VMGWP, ///< velocity made good relative to the waypoint, knots
NMEA_HEEL, ///< boat heel angle, -180 to 180 degrees to starboard
NMEA_PITCH, ///< boat pitch angle, -180 to 180 degrees bow up
NMEA_HDG, ///< magnetic heading, 0 to 360 degrees magnetic
NMEA_HDG_SIN, ///< sine of magnetic heading
NMEA_HDG_COS, ///< cosine of magnetic heading
NMEA_HDT, ///< true heading, 0 to 360 degrees true
NMEA_HDT_SIN, ///< sine of true heading
NMEA_HDT_COS, ///< cosine of true heading
NMEA_VTW, ///< Boat speed through the water in knots
NMEA_LOG, ///< Distance logged through the water in nautical miles
NMEA_LOGR, ///< Distance logged through the water in nautical miles since
///< reset
NMEA_DEPTH, ///< depth of water below the surface in metres
NMEA_RPM_M1, ///< rpm of motor 1
NMEA_TEMPERATURE_M1, ///< temperature of motor 1 in C
NMEA_PRESSURE_M1, ///< pressure of motor 1 in kPa
NMEA_VOLTAGE_M1, ///< voltage of motor 1 in Volts
NMEA_CURRENT_M1, ///< current of motor 1 in Amps
NMEA_RPM_M2, ///< rpm of motor 2
NMEA_TEMPERATURE_M2, ///< temperature of motor 2 in C
NMEA_PRESSURE_M2, ///< pressure of motor 2 in kPa
NMEA_VOLTAGE_M2, ///< voltage of motor 2 in Volts
NMEA_CURRENT_M2, ///< current of motor 2 in Amps
NMEA_TEMPERATURE_AIR, ///< outside temperature in C
NMEA_TEMPERATURE_WATER, ///< sea water temperature in C
NMEA_HUMIDITY, ///< outside relative humidity in %
NMEA_BAROMETER, ///< barometric pressure in Pa absolute -- not altitude
///< corrected
NMEA_USR_00, ///< spaces for a user sketch to inject its own data
NMEA_USR_01, ///< spaces for a user sketch to inject its own data
NMEA_USR_02, ///< spaces for a user sketch to inject its own data
NMEA_USR_03,
NMEA_USR_04,
NMEA_USR_05,
NMEA_USR_06,
NMEA_USR_07,
NMEA_USR_08,
NMEA_USR_09,
NMEA_USR_10,
NMEA_USR_11,
NMEA_USR_12,
NMEA_MAX_INDEX ///< the largest number in the enum type -- not for data,
///< but does define size of data value array required.
} nmea_index_t; ///< Indices for data values expected to change often with time
#endif // _NMEA_DATA_H
@@ -0,0 +1,877 @@
/**************************************************************************/
/*!
@file NMEA_parse.cpp
This is the Adafruit GPS library - the ultimate GPS library
for the ultimate GPS module!
Tested and works great with the Adafruit Ultimate GPS module
using MTK33x9 chipset
------> http://www.adafruit.com/products/746
Adafruit invests time and resources providing this open source code,
please support Adafruit and open-source hardware by purchasing
products from Adafruit!
@author Limor Fried/Ladyada for Adafruit Industries.
@copyright BSD license, check license.txt for more information
All text above must be included in any redistribution
*/
/**************************************************************************/
#include <Adafruit_GPS.h>
/**************************************************************************/
/*!
@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 successfully parsed, false if fails check or parsing
*/
/**************************************************************************/
bool Adafruit_GPS::parse(char *nmea) {
if (!check(nmea))
return false;
// passed the check, so there's a valid source in thisSource and a valid
// sentence in thisSentence
char *p = nmea; // Pointer to move through the sentence -- good parsers are
// non-destructive
p = strchr(p, ',') + 1; // Skip to char after the next comma, then check.
// This may look inefficient, but an M0 will get down the list in about 1 us /
// strcmp()! Put the GPS sentences from Adafruit_GPS at the top to make
// pruning excess code easier. Otherwise, keep them alphabetical for ease of
// reading.
if (!strcmp(thisSentence, "GGA")) { //************************************GGA
// Adafruit from Actisense NGW-1 from SH CP150C
parseTime(p);
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;
p = strchr(p, ',') + 1;
// parse out both longitude and direction, then go to next field, or fail
if (parseCoord(p, &longitudeDegrees, &longitude, &longitude_fixed, &lon))
newDataValue(NMEA_LON, longitudeDegrees);
p = strchr(p, ',') + 1;
p = strchr(p, ',') + 1;
if (!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; // 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))
newDataValue(NMEA_HDOP, HDOP = atof(p));
p = strchr(p, ',') + 1;
if (!isEmpty(p))
altitude = atof(p);
p = strchr(p, ',') + 1;
p = strchr(p, ',') + 1; // skip the units
if (!isEmpty(p))
geoidheight = atof(p); // skip the rest
} else if (!strcmp(thisSentence, "RMC")) { //*****************************RMC
// in Adafruit from Actisense NGW-1 from SH CP150C
parseTime(p);
p = strchr(p, ',') + 1;
parseFix(p);
p = strchr(p, ',') + 1;
// parse out both latitude and direction, then go to next field, or fail
if (parseCoord(p, &latitudeDegrees, &latitude, &latitude_fixed, &lat))
newDataValue(NMEA_LAT, latitudeDegrees);
p = strchr(p, ',') + 1;
p = strchr(p, ',') + 1;
// parse out both longitude and direction, then go to next field, or fail
if (parseCoord(p, &longitudeDegrees, &longitude, &longitude_fixed, &lon))
newDataValue(NMEA_LON, longitudeDegrees);
p = strchr(p, ',') + 1;
p = strchr(p, ',') + 1;
if (!isEmpty(p))
newDataValue(NMEA_SOG, speed = atof(p));
p = strchr(p, ',') + 1;
if (!isEmpty(p))
newDataValue(NMEA_COG, angle = atof(p));
p = strchr(p, ',') + 1;
if (!isEmpty(p)) {
uint32_t fulldate = atof(p);
day = fulldate / 10000;
month = (fulldate % 10000) / 100;
year = (fulldate % 100);
lastDate = sentTime;
} // skip the rest
} 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;
p = strchr(p, ',') + 1;
// parse out both longitude and direction, then go to next field, or fail
if (parseCoord(p, &longitudeDegrees, &longitude, &longitude_fixed, &lon))
newDataValue(NMEA_LON, longitudeDegrees);
p = strchr(p, ',') + 1;
p = strchr(p, ',') + 1;
parseTime(p);
p = strchr(p, ',') + 1;
parseFix(p); // skip the rest
} 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;
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))
VDOP = atof(p); // last before checksum
} else if (!strcmp(thisSentence, "TOP")) { //*****************************TOP
// See:
// https://learn.adafruit.com/adafruit-ultimate-gps-featherwing/antenna-options
// There is an output sentence that will tell you the status of the
// antenna. $PGTOP,11,x where x is the status number. If x is 3 that means
// it is using the external antenna. If x is 2 it's using the internal
p = strchr(p, ',') + 1;
parseAntenna(p);
}
#ifdef NMEA_EXTENSIONS // Sentences not required for basic GPS functionality
else if (!strcmp(thisSentence, "APB")) { //*******************************APB
// from Actisense NGW-1 from SH CP150C
return false;
} else if (!strcmp(thisSentence, "DBT")) { //*****************************DBT
// from Actisense NGW-1
// feet, metres, fathoms below transducer coerced to water depth from
// surface in metres
if (!isEmpty(p))
newDataValue(NMEA_DEPTH,
(nmea_float_t)atof(p) * 0.3048f + depthToTransducer);
p = strchr(p, ',') + 1;
p = strchr(p, ',') + 1;
if (!isEmpty(p))
newDataValue(NMEA_DEPTH, (nmea_float_t)atof(p) + depthToTransducer);
p = strchr(p, ',') + 1;
p = strchr(p, ',') + 1;
if (!isEmpty(p))
newDataValue(NMEA_DEPTH,
(nmea_float_t)atof(p) * 6 * 0.3048f + 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.8f;
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.8f;
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.8f;
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.6f;
units = 'M';
}
if (units == 'M') {
spd *= 5280.0f / 6000.0f;
units = 'N';
}
if (ang > 180.0f)
ang -= 360.0f;
if (ref == 'R') {
if (ang < 1000.0f && stat == 'A')
newDataValue(NMEA_AWA, ang);
if (spd < 1000.0f && stat == 'A')
newDataValue(NMEA_AWS, spd);
} else {
if (ang < 1000.0f && stat == 'A')
newDataValue(NMEA_TWA, ang);
if (spd < 1000.0f && stat == 'A')
newDataValue(NMEA_TWS, spd);
}
} else if (!strcmp(thisSentence, "RMB")) { //*****************************RMB
// from Actisense NGW-1 from SH CP150C
// 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
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.0f && xteDir != 'X') {
if (xteDir == 'L')
xte *= -1.0f;
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;
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 = 'X';
char lonWP = 'X';
// 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;
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;
if (!isEmpty(p))
txtN = atoi(p);
p = strchr(p, ',') + 1;
if (!isEmpty(p))
txtID = atoi(p);
p = strchr(p, ',') + 1;
if (!isEmpty(p))
parseStr(txtTXT, p, 61); // copy the text to NMEA TXT max of 61 characters
} 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.0f)
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.0f)
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.6f;
units = 'K';
} // convert m/s to km/h
if (units == 'K') {
ws /= 1.6f;
units = 'M';
} // convert km/h to miles / h
if (units == 'M') {
ws *= 5280.0f / 6000.0f;
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.0f && xteDir != 'X') {
if (xteDir == 'L')
xte *= -1.0f;
newDataValue(NMEA_XTE, xte);
} // skip units
} else if (!strcmp(thisSentence, "ZDA")) { //*****************************ZDA
// from Actisense NGW-1
return false;
}
#endif // NMEA_EXTENSIONS
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);
strcpy(lastSentence, thisSentence);
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
*/
/**************************************************************************/
bool Adafruit_GPS::check(char *nmea) {
thisCheck = 0; // new check
*thisSentence = *thisSource = 0;
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; // known but not parsed
} else {
parseStr(thisSentence, p, NMEA_MAX_SENTENCE_ID);
return false; // unknown
}
}
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] = {0}; // Ensure string is terminated after strncpy
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(int(e - p), n - 1);
strncpy(buff, p, len); // copy up to the comma
buff[len] = 0;
} else {
e = strchr(p, '*');
if (e) {
len = min(int(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);
char *dec = strchr(p, '.');
char *comstar = min(strchr(p, ','), strchr(p, '*'));
if (dec != NULL && comstar != NULL && dec < comstar)
milliseconds = atof(dec) * 1000;
else
milliseconds = 0;
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
*/
/**************************************************************************/
bool 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 Parse a part of an NMEA string for antenna that is used
@param p Pointer to the location of the token in the NMEA string
@return 3=external 2=internal 1=there was an antenna short or problem
*/
/**************************************************************************/
bool Adafruit_GPS::parseAntenna(char *p) {
if (!isEmpty(p)) {
if (p[0] == '3') {
antenna = 3;
} else if (p[0] == '2') {
antenna = 2;
} else if (p[0] == '1') {
antenna = 1;
} 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;
}