esp32_Adafruit_GPS/src/Adafruit_GPS.cpp

688 lines
22 KiB
C++
Raw Normal View History

/**************************************************************************/
/*!
@file Adafruit_GPS.cpp
2012-03-27 22:56:07 +01:00
@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
*/
/**************************************************************************/
2012-03-27 22:56:07 +01:00
#include <Adafruit_GPS.h>
static bool strStartsWith(const char *str, const char *prefix);
/**************************************************************************/
/*!
@brief Is the field empty, or should we try conversion? Won't work
2020-01-18 15:23:14 +00:00
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
*/
/**************************************************************************/
/*!
@brief Parse a part of an NMEA string for latitude angle
@param p Pointer to the location of the token in the NMEA string
*/
/**************************************************************************/
// void Adafruit_GPS::parseLat(char *p) {
// char degreebuff[10];
// if (!isEmpty(p)) {
// strncpy(degreebuff, p, 2);
// p += 2;
// degreebuff[2] = '\0';
// long degree = atol(degreebuff) * 10000000;
// strncpy(degreebuff, p, 2); // minutes
// p += 3; // skip decimal point
// strncpy(degreebuff + 2, p, 4);
// degreebuff[6] = '\0';
// long minutes = 50 * atol(degreebuff) / 3;
// latitude_fixed = degree + minutes;
// latitude = degree / 100000 + minutes * 0.000006F;
// latitudeDegrees = (latitude - 100 * int(latitude / 100)) / 60.0f;
// latitudeDegrees += int(latitude / 100);
// }
// }
/**************************************************************************/
/*!
@brief Parse a part of an NMEA string for latitude direction
@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::parseLatDir(char *p) {
// if (p[0] == 'S') {
// lat = 'S';
// latitudeDegrees *= -1.0f;
// latitude_fixed *= -1;
// } else if (p[0] == 'N') {
// lat = 'N';
// } else if (p[0] == ',') {
// lat = 0;
// } else {
// return false;
// }
// return true;
// }
/**************************************************************************/
/*!
@brief Parse a part of an NMEA string for longitude angle
@param p Pointer to the location of the token in the NMEA string
*/
/**************************************************************************/
// void Adafruit_GPS::parseLon(char *p) {
// int32_t degree;
// long minutes;
// char degreebuff[10];
// if (!isEmpty(p)) {
// strncpy(degreebuff, p, 3);
// p += 3;
// degreebuff[3] = '\0';
// degree = atol(degreebuff) * 10000000;
// strncpy(degreebuff, p, 2); // minutes
// p += 3; // skip decimal point
// strncpy(degreebuff + 2, p, 4);
// degreebuff[6] = '\0';
// minutes = 50 * atol(degreebuff) / 3;
// longitude_fixed = degree + minutes;
// longitude = degree / 100000 + minutes * 0.000006F;
// longitudeDegrees = (longitude - 100 * int(longitude / 100)) / 60.0f;
// longitudeDegrees += int(longitude / 100);
// }
// }
/**************************************************************************/
/*!
@brief Parse a part of an NMEA string for longitude direction
@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::parseLonDir(char *p) {
// if (!isEmpty(p)) {
// if (p[0] == 'W') {
// lon = 'W';
// longitudeDegrees *= -1.0f;
// longitude_fixed *= -1;
// } else if (p[0] == 'E') {
// lon = 'E';
// } else if (p[0] == ',') {
// lon = 0;
// } else {
// return false;
// }
// }
// return true;
// }
/**************************************************************************/
/*!
@brief Time in seconds since the last position fix was obtained. Will
fail by rolling over to zero after one millis() cycle, about 6-1/2 weeks.
@return nmea_float_t value in seconds since last fix.
*/
/**************************************************************************/
2020-01-19 14:37:51 +00:00
nmea_float_t Adafruit_GPS::secondsSinceFix() {
2020-01-19 14:52:28 +00:00
return (millis() - lastFix) / 1000.;
2020-01-19 14:15:18 +00:00
}
2012-03-27 22:56:07 +01:00
/**************************************************************************/
/*!
@brief Time in seconds since the last GPS time was obtained. Will fail
by rolling over to zero after one millis() cycle, about 6-1/2 weeks.
@return nmea_float_t value in seconds since last GPS time.
*/
/**************************************************************************/
2020-01-19 14:37:51 +00:00
nmea_float_t Adafruit_GPS::secondsSinceTime() {
2020-01-19 14:52:28 +00:00
return (millis() - lastTime) / 1000.;
2020-01-19 14:15:18 +00:00
}
/**************************************************************************/
/*!
@brief Time in seconds since the last GPS date was obtained. Will fail
by rolling over to zero after one millis() cycle, about 6-1/2 weeks.
@return nmea_float_t value in seconds since last GPS date.
*/
/**************************************************************************/
2020-01-19 14:37:51 +00:00
nmea_float_t Adafruit_GPS::secondsSinceDate() {
2020-01-19 14:52:28 +00:00
return (millis() - lastDate) / 1000.;
2020-01-19 14:15:18 +00:00
}
/**************************************************************************/
/*!
@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(); }
2019-10-14 02:32:50 +01:00
/**************************************************************************/
/*!
2020-01-17 21:18:09 +00:00
@brief How many bytes are available to read - part of 'Print'-class
functionality
2019-10-14 02:32:50 +01:00
@return Bytes available, 0 if none
*/
/**************************************************************************/
size_t Adafruit_GPS::available(void) {
2020-01-17 21:18:09 +00:00
if (paused)
return 0;
#if (defined(__AVR__) || defined(ESP8266)) && defined(USE_SW_SERIAL)
if (gpsSwSerial) {
return gpsSwSerial->available();
2019-10-31 21:05:02 +00:00
}
#endif
if (gpsHwSerial) {
return gpsHwSerial->available();
}
2019-11-17 00:06:07 +00:00
if (gpsI2C || gpsSPI) {
2020-01-17 21:18:09 +00:00
return 1; // I2C/SPI doesnt have 'availability' so always has a byte at
// least to read!
}
2019-10-09 21:51:17 +01:00
return 0;
}
2019-10-14 02:32:50 +01:00
/**************************************************************************/
/*!
2020-01-17 21:18:09 +00:00
@brief Write a byte to the underlying transport - part of 'Print'-class
functionality
2019-10-14 02:32:50 +01:00
@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(ESP8266)) && defined(USE_SW_SERIAL)
if (gpsSwSerial) {
return gpsSwSerial->write(c);
2019-10-31 21:05:02 +00:00
}
#endif
if (gpsHwSerial) {
return gpsHwSerial->write(c);
}
if (gpsI2C) {
gpsI2C->beginTransmission(_i2caddr);
if (gpsI2C->write(c) != 1) {
return 0;
}
if (gpsI2C->endTransmission(true) == 0) {
return 1;
}
}
2019-11-17 00:06:07 +00:00
if (gpsSPI) {
2020-01-17 21:18:09 +00:00
gpsSPI->beginTransaction(gpsSPI_settings);
2019-11-17 00:06:07 +00:00
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
@return The character that we received, or 0 if nothing was available
*/
/**************************************************************************/
char Adafruit_GPS::read(void) {
2020-01-17 21:18:09 +00:00
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)
2020-01-17 21:18:09 +00:00
return c;
2012-03-27 22:56:07 +01:00
#if (defined(__AVR__) || defined(ESP8266)) && defined(USE_SW_SERIAL)
2020-01-17 21:18:09 +00:00
if (gpsSwSerial) {
2019-10-31 21:05:02 +00:00
if (!gpsSwSerial->available())
return c;
c = gpsSwSerial->read();
2019-10-31 21:05:02 +00:00
}
2014-06-08 20:46:05 +01:00
#endif
if (gpsHwSerial) {
2019-10-31 21:05:02 +00:00
if (!gpsHwSerial->available())
return c;
c = gpsHwSerial->read();
}
if (gpsI2C) {
2019-11-17 00:06:07 +00:00
if (_buff_idx <= _buff_max) {
c = _i2cbuffer[_buff_idx];
_buff_idx++;
} else {
// refill the buffer!
2020-01-17 21:18:09 +00:00
if (gpsI2C->requestFrom(0x10, GPS_MAX_I2C_TRANSFER, 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;
}
}
2019-10-31 21:05:02 +00:00
2019-11-17 00:06:07 +00:00
if (gpsSPI) {
do {
2020-01-17 21:18:09 +00:00
gpsSPI->beginTransaction(gpsSPI_settings);
2019-11-17 00:06:07 +00:00
if (gpsSPI_cs >= 0) {
2020-01-17 21:18:09 +00:00
digitalWrite(gpsSPI_cs, LOW);
2019-11-17 00:06:07 +00:00
}
c = gpsSPI->transfer(0xFF);
if (gpsSPI_cs >= 0) {
2020-01-17 21:18:09 +00:00
digitalWrite(gpsSPI_cs, HIGH);
2019-11-17 00:06:07 +00:00
}
gpsSPI->endTransaction();
// skip duplicate 0x0A's - but keep as part of a CRLF
2020-01-17 21:18:09 +00:00
} while (((c == 0x0A) && (last_char != 0x0D)) ||
(!isprint(c) && !isspace(c)));
2019-11-17 00:06:07 +00:00
last_char = c;
}
2020-01-17 21:18:09 +00:00
// Serial.print(c);
2019-10-31 21:05:02 +00:00
currentline[lineidx++] = c;
if (lineidx >= MAXLINELENGTH)
2020-01-17 21:18:09 +00:00
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;
2012-03-27 22:56:07 +01:00
}
2020-01-17 21:18:09 +00:00
// Serial.println("----");
// Serial.println((char *)lastline);
// Serial.println("----");
lineidx = 0;
recvdflag = true;
2020-01-17 21:18:09 +00:00
recvdTime = millis(); // time we got the end of the string
sentTime = firstChar;
2020-01-17 21:18:09 +00:00
firstChar = 0; // there are no characters yet
return c; // wait until next character to set time
2012-03-27 22:56:07 +01:00
}
2020-01-17 21:18:09 +00:00
if (firstChar == 0)
firstChar = tStart;
return c;
2012-03-27 22:56:07 +01:00
}
/**************************************************************************/
/*!
@brief Constructor when using SoftwareSerial
@param ser Pointer to SoftwareSerial device
*/
/**************************************************************************/
#if (defined(__AVR__) || defined(ESP8266)) && defined(USE_SW_SERIAL)
2020-01-17 21:18:09 +00:00
Adafruit_GPS::Adafruit_GPS(SoftwareSerial *ser) {
common_init(); // Set everything to common state, then...
gpsSwSerial = ser; // ...override gpsSwSerial with value passed.
2012-03-27 22:56:07 +01:00
}
2014-06-08 20:46:05 +01:00
#endif
2012-03-27 22:56:07 +01:00
/**************************************************************************/
/*!
@brief Constructor when using HardwareSerial
@param ser Pointer to a HardwareSerial object
*/
/**************************************************************************/
Adafruit_GPS::Adafruit_GPS(HardwareSerial *ser) {
2020-01-17 21:18:09 +00:00
common_init(); // Set everything to common state, then...
gpsHwSerial = ser; // ...override gpsHwSerial with value passed.
2012-03-27 22:56:07 +01:00
}
/**************************************************************************/
/*!
@brief Constructor when using I2C
@param theWire Pointer to an I2C TwoWire object
*/
/**************************************************************************/
Adafruit_GPS::Adafruit_GPS(TwoWire *theWire) {
2020-01-17 21:18:09 +00:00
common_init(); // Set everything to common state, then...
gpsI2C = theWire; // ...override gpsI2C
}
2019-11-17 00:06:07 +00:00
/**************************************************************************/
/*!
@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) {
2020-01-17 21:18:09 +00:00
common_init(); // Set everything to common state, then...
2019-11-17 00:06:07 +00:00
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
*/
/**************************************************************************/
2012-03-27 22:56:07 +01:00
void Adafruit_GPS::common_init(void) {
#if (defined(__AVR__) || defined(ESP8266)) && defined(USE_SW_SERIAL)
gpsSwSerial = NULL; // Set both to NULL, then override correct
2014-06-08 20:46:05 +01:00
#endif
gpsHwSerial = NULL; // port pointer in corresponding constructor
2020-01-17 21:18:09 +00:00
gpsI2C = NULL;
gpsSPI = NULL;
recvdflag = false;
paused = false;
lineidx = 0;
currentline = line1;
2020-01-17 21:18:09 +00:00
lastline = line2;
2020-01-17 21:18:09 +00:00
hour = minute = seconds = year = month = day = fixquality = fixquality_3d =
satellites = 0; // uint8_t
lat = lon = mag = 0; // char
fix = false; // bool
2020-01-17 21:18:09 +00:00
milliseconds = 0; // uint16_t
latitude = longitude = geoidheight = altitude = speed = angle = magvariation =
HDOP = VDOP = PDOP = 0.0; // nmea_float_t
2020-01-29 15:35:00 +00:00
#ifdef NMEA_EXTENSIONS
data_init();
#endif
2012-03-27 22:56:07 +01:00
}
/**************************************************************************/
/*!
@brief Destroy the object.
@return none
*/
/**************************************************************************/
Adafruit_GPS::~Adafruit_GPS() {
#ifdef NMEA_EXTENSIONS
for (int i = 0; i < (int)NMEA_MAX_INDEX; i++)
removeHistory((nmea_index_t)i); // to free any history mallocs
#endif
}
/**************************************************************************/
/*!
@brief Start the HW or SW serial port
@param baud_or_i2caddr Baud rate if using serial, I2C address if using I2C
@returns True on successful hardware init, False on failure
*/
/**************************************************************************/
2020-01-17 21:18:09 +00:00
bool Adafruit_GPS::begin(uint32_t baud_or_i2caddr) {
#if (defined(__AVR__) || defined(ESP8266)) && defined(USE_SW_SERIAL)
2020-01-17 21:18:09 +00:00
if (gpsSwSerial) {
gpsSwSerial->begin(baud_or_i2caddr);
2019-10-31 21:05:02 +00:00
}
2014-06-08 20:46:05 +01:00
#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);
2020-01-17 21:18:09 +00:00
return (gpsI2C->endTransmission() == 0);
}
2019-11-17 00:06:07 +00:00
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);
}
}
2012-12-04 16:20:10 +00:00
delay(10);
return true;
}
2012-03-27 22:56:07 +01:00
/**************************************************************************/
/*!
@brief Send a command to the GPS device
@param str Pointer to a string holding the command to send
*/
/**************************************************************************/
2020-01-17 21:18:09 +00:00
void Adafruit_GPS::sendCommand(const char *str) { println(str); }
2012-03-27 22:56:07 +01:00
/**************************************************************************/
/*!
@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; }
2012-03-27 22:56:07 +01:00
/**************************************************************************/
/*!
@brief Pause/unpause receiving new data
@param p True = pause, false = unpause
*/
/**************************************************************************/
void Adafruit_GPS::pause(bool p) { paused = p; }
2012-03-27 22:56:07 +01:00
/**************************************************************************/
/*!
@brief Returns the last NMEA line received and unsets the received flag
@return Pointer to the last line string
*/
/**************************************************************************/
2012-03-27 22:56:07 +01:00
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
2020-01-17 21:18:09 +00:00
@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) {
2020-01-17 21:18:09 +00:00
uint8_t i = 0;
2012-03-28 19:14:32 +01:00
while (i < max) {
if (!usingInterrupts)
read();
if (newNMEAreceived()) {
2012-03-28 19:14:32 +01:00
char *nmea = lastNMEA();
i++;
if (strStartsWith(nmea, wait4me))
2020-01-17 21:18:09 +00:00
return true;
2012-03-28 19:14:32 +01:00
}
}
return false;
}
/**************************************************************************/
/*!
@brief Start the LOCUS logger
@return True on success, false if it failed
*/
/**************************************************************************/
bool Adafruit_GPS::LOCUS_StartLogger(void) {
2012-03-28 19:14:32 +01:00
sendCommand(PMTK_LOCUS_STARTLOG);
recvdflag = false;
2013-10-27 16:15:00 +00:00
return waitForSentence(PMTK_LOCUS_STARTSTOPACK);
}
/**************************************************************************/
/*!
@brief Stop the LOCUS logger
@return True on success, false if it failed
*/
/**************************************************************************/
bool Adafruit_GPS::LOCUS_StopLogger(void) {
2013-10-27 16:15:00 +00:00
sendCommand(PMTK_LOCUS_STOPLOG);
recvdflag = false;
return waitForSentence(PMTK_LOCUS_STARTSTOPACK);
2012-03-28 19:14:32 +01:00
}
/**************************************************************************/
/*!
@brief Read the logger status
@return True if we read the data, false if there was no response
*/
/**************************************************************************/
bool Adafruit_GPS::LOCUS_ReadStatus(void) {
2012-03-28 19:14:32 +01:00
sendCommand(PMTK_LOCUS_QUERY_STATUS);
2020-01-17 21:18:09 +00:00
if (!waitForSentence("$PMTKLOG"))
2012-03-28 19:14:32 +01:00
return false;
char *response = lastNMEA();
uint16_t parsed[10];
uint8_t i;
2020-01-17 21:18:09 +00:00
for (i = 0; i < 10; i++)
parsed[i] = -1;
2012-03-28 19:14:32 +01:00
response = strchr(response, ',');
2020-01-17 21:18:09 +00:00
for (i = 0; i < 10; i++) {
if (!response || (response[0] == 0) || (response[0] == '*'))
2012-03-28 19:14:32 +01:00
break;
response++;
2020-01-17 21:18:09 +00:00
parsed[i] = 0;
while ((response[0] != ',') && (response[0] != '*') && (response[0] != 0)) {
2012-03-28 19:14:32 +01:00
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;
2012-03-28 19:14:32 +01:00
}
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) {
2020-01-17 21:18:09 +00:00
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);
2020-01-17 21:18:09 +00:00
// 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) {
2020-01-17 21:18:09 +00:00
inStandbyMode = false;
sendCommand(""); // send byte to wake it up
return waitForSentence(PMTK_AWAKE);
2020-01-17 21:18:09 +00:00
} else {
return false; // Returns false if not in standby mode, nothing to wakeup
}
2012-12-04 16:20:10 +00:00
}
/**************************************************************************/
/*!
@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;
}