mirror of
https://github.com/adafruit/Adafruit_GPS.git
synced 2026-10-26 22:10:17 +00:00
Expose exact received positions through the GPS API
Co-authored-by: Limor Fried <ladyada@users.noreply.github.com>
This commit is contained in:
1 parent
0c9b45f58b
commit
0a7b9caef4
4 files changed
+218
-1
No files matched your search
@@ -42,6 +42,48 @@ may not yet include the latest merged fixes.
|
||||
| I2C | Your GPS exposes SDA and SCL, as the PA1010D does. | Connect SDA, SCL, power, and ground. Examples use address `0x10`; on an Uno or Metro Mini, SDA is A4 and SCL is A5. |
|
||||
| SPI | Your GPS specifically supports SPI. | Follow its wiring requirements; the echo example uses CS on D10 and reset on D9. |
|
||||
|
||||
## Exact received positions
|
||||
|
||||
`GPS.lastPosition()` returns the latest received GGA, RMC, or GLL sentence as
|
||||
an independent `gnss_position_t`. Its coordinate components retain all nine
|
||||
fractional-minute digits without a global float-type override. Use
|
||||
`Adafruit_GNSS::formatCoordinate()` to print them as decimal degrees on any board,
|
||||
including AVR. Preserving digits does not increase the receiver's accuracy.
|
||||
|
||||
For a sketch that polls `GPS.read()` in `loop()`, after initializing `GPS`:
|
||||
|
||||
```cpp
|
||||
GPS.read();
|
||||
if (GPS.newNMEAreceived()) {
|
||||
gnss_position_t position = GPS.lastPosition();
|
||||
GPS.lastNMEA(); // Acknowledge this line; lastPosition() leaves the flag alone.
|
||||
if (position.validation.status == GNSS_SENTENCE_VALID &&
|
||||
position.fixStatus == NMEA_NUMBER_VALID && position.fix &&
|
||||
position.latitude.status == NMEA_NUMBER_VALID &&
|
||||
position.longitude.status == NMEA_NUMBER_VALID) {
|
||||
char coordinate[GNSS_COORDINATE_TEXT_SIZE];
|
||||
Adafruit_GNSS::formatCoordinate(coordinate, sizeof(coordinate), position.latitude);
|
||||
Serial.print(coordinate);
|
||||
Serial.print(F(", "));
|
||||
Adafruit_GNSS::formatCoordinate(coordinate, sizeof(coordinate), position.longitude);
|
||||
Serial.println(coordinate);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The result describes one sentence, without merging previous fixes. Empty fields
|
||||
stay empty, and replies or other sentence types return `GNSS_SENTENCE_UNSUPPORTED`.
|
||||
Before any complete line, or for a line with an invalid frame/checksum, the result
|
||||
is `GNSS_SENTENCE_INVALID_FRAME`. Partial or oversized input leaves the previous
|
||||
complete line available. If an interrupt calls `GPS.read()`, synchronize it with
|
||||
`lastPosition()` so the receive buffer cannot change during decoding.
|
||||
|
||||
This getter does not update `GPS.fix`, `GPS.latitudeDegrees`, or other legacy
|
||||
fields; continue using `GPS.parse(GPS.lastNMEA())` when those fields are needed.
|
||||
Likewise, parsing a separate caller-supplied string does not change this getter's
|
||||
received sentence. Decode such a string with `Adafruit_NMEA::validate()` followed
|
||||
by `Adafruit_GNSS::parsePosition()` instead.
|
||||
|
||||
## Example guide
|
||||
|
||||
Every example is linked below. An **echo** sketch shows the GPS's raw text;
|
||||
|
||||
@@ -0,0 +1,148 @@
|
||||
#include <Adafruit_GPS.h>
|
||||
|
||||
class TestStream : public Stream {
|
||||
public:
|
||||
const char *input = "";
|
||||
int available() { return *input != 0; }
|
||||
int read() { return *input ? *input++ : -1; }
|
||||
int peek() { return *input ? *input : -1; }
|
||||
void flush() {}
|
||||
size_t write(uint8_t) { return 1; }
|
||||
};
|
||||
|
||||
TestStream firstStream, secondStream;
|
||||
Adafruit_GPS firstGPS((Stream *)&firstStream), secondGPS((Stream *)&secondStream);
|
||||
bool testsPassed = false;
|
||||
bool receiveBody(TestStream &stream, Adafruit_GPS &gps, const char *body);
|
||||
bool noPosition(const gnss_position_t &position, gnss_sentence_status_t status);
|
||||
|
||||
void setup() {
|
||||
Serial.begin(115200);
|
||||
Serial.println(F("Adafruit GPS exact received position regression"));
|
||||
const Adafruit_GPS &view = firstGPS;
|
||||
if (!noPosition(view.lastPosition(), GNSS_SENTENCE_INVALID_FRAME) ||
|
||||
firstGPS.newNMEAreceived()) {
|
||||
Serial.println(F("FAIL: getter before reception"));
|
||||
return;
|
||||
}
|
||||
firstGPS.fix = false;
|
||||
firstGPS.latitude_fixed = 42;
|
||||
firstGPS.year = 99;
|
||||
if (!receiveBody(firstStream, firstGPS,
|
||||
"GNRMC,123519.123,A,8959.123456789,S,17959.123456789,W,1,2,290224")) {
|
||||
Serial.println(F("FAIL: receive first exact position"));
|
||||
return;
|
||||
}
|
||||
gnss_position_t saved = view.lastPosition();
|
||||
char coordinate[GNSS_COORDINATE_TEXT_SIZE];
|
||||
if (saved.validation.status != GNSS_SENTENCE_VALID || !saved.fix ||
|
||||
saved.latitude.fractionalMinutes != 123456789 || saved.date.day != 29 ||
|
||||
saved.time.millisecond != 123 || !firstGPS.newNMEAreceived() ||
|
||||
firstGPS.fix || firstGPS.latitude_fixed != 42 || firstGPS.year != 99 ||
|
||||
!Adafruit_GNSS::formatCoordinate(coordinate, sizeof(coordinate), saved.longitude) ||
|
||||
strcmp(coordinate, "-179.98539094648")) {
|
||||
Serial.println(F("FAIL: exact result, acknowledgement flag, or legacy state changed"));
|
||||
return;
|
||||
}
|
||||
if (!firstGPS.parse(firstGPS.lastNMEA()) || firstGPS.newNMEAreceived() ||
|
||||
!firstGPS.fix || firstGPS.latitude_fixed != saved.latitude.degreesE7 ||
|
||||
view.lastPosition().longitude.fractionalMinutes != 123456789) {
|
||||
Serial.println(F("FAIL: legacy parse and exact getter coexistence"));
|
||||
return;
|
||||
}
|
||||
Serial.println(F("PASS: exact output on float builds without changing receive or legacy state"));
|
||||
|
||||
firstStream.input = "$unfinished";
|
||||
while (firstStream.available()) firstGPS.read();
|
||||
if (firstGPS.newNMEAreceived() ||
|
||||
view.lastPosition().latitude.fractionalMinutes != 123456789 ||
|
||||
!receiveBody(firstStream, firstGPS,
|
||||
"GNRMC,123520,A,8959.123456889,S,17959.123456889,W,1,2,290224")) {
|
||||
Serial.println(F("FAIL: partial sentence or recovery"));
|
||||
return;
|
||||
}
|
||||
gnss_position_t next = view.lastPosition();
|
||||
if (next.latitude.degreesE7 != saved.latitude.degreesE7 ||
|
||||
next.latitude.fractionalMinutes != 123456889 ||
|
||||
saved.latitude.fractionalMinutes != 123456789 ||
|
||||
!Adafruit_GNSS::formatCoordinate(coordinate, sizeof(coordinate), next.longitude) ||
|
||||
strcmp(coordinate, "-179.98539094815")) {
|
||||
Serial.println(F("FAIL: submillimeter change or owned result"));
|
||||
return;
|
||||
}
|
||||
Serial.println(F("PASS: new sentences retain changes smaller than E7 and preserve copied results"));
|
||||
|
||||
if (!receiveBody(secondStream, secondGPS,
|
||||
"GBGGA,010203,4807.038,N,01131.000,E,4,08,0.9,545.4,M,46.9") ||
|
||||
secondGPS.lastPosition().fixQuality != 4 ||
|
||||
secondGPS.lastPosition().latitude.degreesE7 != 481173000 ||
|
||||
view.lastPosition().latitude.fractionalMinutes != 123456889) {
|
||||
Serial.println(F("FAIL: independent receivers or shared talker routing"));
|
||||
return;
|
||||
}
|
||||
char separate[120] = "$GPGLL,4807.038,N,01131.000,E,123519,A";
|
||||
firstGPS.addChecksum(separate);
|
||||
if (!firstGPS.parse(separate) ||
|
||||
view.lastPosition().latitude.fractionalMinutes != 123456889 ||
|
||||
!firstGPS.newNMEAreceived()) {
|
||||
Serial.println(F("FAIL: parsing a supplied string changed the received snapshot"));
|
||||
return;
|
||||
}
|
||||
Serial.println(F("PASS: supplied-buffer parsing and separate receivers remain independent"));
|
||||
|
||||
firstGPS.lastNMEA();
|
||||
if (!receiveBody(firstStream, firstGPS, "GNRMC,,V,,,,,,,") ||
|
||||
view.lastPosition().validation.status != GNSS_SENTENCE_VALID ||
|
||||
view.lastPosition().latitude.status != NMEA_NUMBER_EMPTY ||
|
||||
view.lastPosition().fix || !firstGPS.fix) {
|
||||
Serial.println(F("FAIL: empty/no-fix line reused a cached position"));
|
||||
return;
|
||||
}
|
||||
firstGPS.lastNMEA();
|
||||
if (!receiveBody(firstStream, firstGPS, "PAIR001,062,0") ||
|
||||
!noPosition(view.lastPosition(), GNSS_SENTENCE_UNSUPPORTED) ||
|
||||
!firstGPS.newNMEAreceived() || strncmp(firstGPS.lastNMEA(), "$PAIR001,", 9)) {
|
||||
Serial.println(F("FAIL: command reply replaced or acknowledged by getter"));
|
||||
return;
|
||||
}
|
||||
firstStream.input = "$GPGLL,4807.038,N,01131.000,E,123519,A*00\n";
|
||||
while (firstStream.available()) firstGPS.read();
|
||||
if (!firstGPS.newNMEAreceived() ||
|
||||
!noPosition(view.lastPosition(), GNSS_SENTENCE_INVALID_FRAME)) {
|
||||
Serial.println(F("FAIL: bad checksum accepted or acknowledged"));
|
||||
return;
|
||||
}
|
||||
firstGPS.common_init();
|
||||
if (!noPosition(view.lastPosition(), GNSS_SENTENCE_INVALID_FRAME) ||
|
||||
firstGPS.newNMEAreceived() || secondGPS.lastPosition().fixQuality != 4 ||
|
||||
saved.latitude.fractionalMinutes != 123456789) {
|
||||
Serial.println(F("FAIL: reset, receiver isolation, or result lifetime"));
|
||||
return;
|
||||
}
|
||||
Serial.println(F("PASS: no-fix, replies, bad checksum, and reset never reuse stale positions"));
|
||||
testsPassed = true;
|
||||
}
|
||||
|
||||
void loop() {
|
||||
if (testsPassed) Serial.println(F("PASS: exact received position regression"));
|
||||
else Serial.println(F("FAIL: exact received position regression"));
|
||||
}
|
||||
|
||||
bool receiveBody(TestStream &stream, Adafruit_GPS &gps, const char *body) {
|
||||
char line[120];
|
||||
if (!Adafruit_NMEA::buildCommand(line, sizeof(line), body, strlen(body))) return false;
|
||||
stream.input = line;
|
||||
while (stream.available()) gps.read();
|
||||
stream.input = "";
|
||||
return gps.newNMEAreceived();
|
||||
}
|
||||
|
||||
bool noPosition(const gnss_position_t &position, gnss_sentence_status_t status) {
|
||||
return position.validation.status == status &&
|
||||
position.latitude.status == NMEA_NUMBER_MISSING &&
|
||||
position.longitude.status == NMEA_NUMBER_MISSING &&
|
||||
position.time.status == NMEA_NUMBER_MISSING &&
|
||||
position.date.status == NMEA_NUMBER_MISSING &&
|
||||
position.fixStatus == NMEA_NUMBER_MISSING &&
|
||||
position.fixQualityStatus == NMEA_NUMBER_MISSING && !position.fix;
|
||||
}
|
||||
@@ -405,6 +405,32 @@ char *Adafruit_GPS::lastNMEA(void) {
|
||||
return (char *)lastline;
|
||||
}
|
||||
|
||||
/**************************************************************************/
|
||||
/*!
|
||||
@brief Decode the latest received sentence with exact coordinate precision.
|
||||
@return Independent GGA/RMC/GLL position data and per-field status.
|
||||
|
||||
Call after read() completes a line. This does not acknowledge the receive
|
||||
flag, update legacy GPS fields, or merge data from earlier sentences. Use
|
||||
lastNMEA() to acknowledge the line and parse() to update legacy fields.
|
||||
Parsing a caller-supplied string does not replace the received sentence.
|
||||
|
||||
Check validation and each field's status; a valid fix flag does not imply
|
||||
populated coordinates. Exact components can be passed to
|
||||
Adafruit_GNSS::formatCoordinate() even when nmea_float_t is float.
|
||||
No stored fix or extra receive buffer is added. The result owns its values
|
||||
and survives subsequent input. Calls must be synchronized with read(),
|
||||
including when read() runs in an interrupt.
|
||||
|
||||
Before reception or after common_init(), returns INVALID_FRAME. Invalid
|
||||
complete lines return INVALID_FRAME; valid non-position sentences return
|
||||
UNSUPPORTED. Partial or oversized input preserves the last complete line.
|
||||
*/
|
||||
/**************************************************************************/
|
||||
gnss_position_t Adafruit_GPS::lastPosition() const {
|
||||
return receiver.lastPosition();
|
||||
}
|
||||
|
||||
/**************************************************************************/
|
||||
/*!
|
||||
* @brief Wait for a specified sentence from the device
|
||||
|
||||
+2
-1
@@ -106,6 +106,7 @@ public:
|
||||
bool newNMEAreceived();
|
||||
void pause(bool b);
|
||||
char *lastNMEA(void);
|
||||
gnss_position_t lastPosition() const;
|
||||
bool waitForSentence(const char *wait, uint8_t max = MAXWAITSENTENCE,
|
||||
bool usingInterrupts = false, uint32_t timeout = 10000);
|
||||
bool LOCUS_StartLogger(void);
|
||||
@@ -293,7 +294,7 @@ private:
|
||||
volatile char line1[MAXLINELENGTH]; ///< We double buffer: read one line in
|
||||
///< and leave one for the main program
|
||||
volatile char line2[MAXLINELENGTH]; ///< Second buffer
|
||||
Adafruit_NMEA receiver{line1, line2, MAXLINELENGTH}; ///< Bounded byte framing
|
||||
Adafruit_GNSS receiver{line1, line2, MAXLINELENGTH}; ///< Shared GNSS receiver
|
||||
const char *volatile lastline =
|
||||
(const char *)line2; ///< Completed line published by read()
|
||||
volatile bool recvdflag; ///< Received flag
|
||||
|
||||
Reference in new issue
Block a user