initial commit
This commit is contained in:
@@ -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;
|
||||
}
|
||||
Reference in New Issue
Block a user