Skip to content

Commit e95e692

Browse files
authored
Merge pull request #12 from sparkfun/AddPPPNAVSupport
Add support for HAS and PPPNAV
2 parents 50946d7 + a3b700f commit e95e692

File tree

5 files changed

+285
-6
lines changed

5 files changed

+285
-6
lines changed
Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
/*
2+
Enable HAS corrections
3+
By: Nathan Seidle and Mikal Hart
4+
Date: January 3, 2026
5+
License: Public domain - do whatever you'd like.
6+
7+
This example shows how to enable and monitor the High Accuracy Service (HAS) convergence.
8+
9+
Feel like supporting open source hardware?
10+
Buy a board from SparkFun!
11+
LG290P Breakout: https://www.sparkfun.com/sparkfun-quadband-gnss-rtk-breakout-lg290p-qwiic.html
12+
LG290P PiHat: https://www.sparkfun.com/sparkfun-gnss-flex-phat-lg290p.html
13+
14+
Hardware Connections:
15+
Connect RX3 (green wire) of the LG290P to pin 14 on the ESP32
16+
Connect TX3 (orange wire) of the LG290P to pin 13 on the ESP32
17+
To make this easier, a 4-pin locking JST cable can be purchased here: https://www.sparkfun.com/products/17240
18+
Note: Almost any ESP32 pins can be used for serial.
19+
Connect a multi-band GNSS antenna: https://www.sparkfun.com/products/21801
20+
*/
21+
22+
#include <SparkFun_LG290P_GNSS.h> // Click here to get the library: http://librarymanager/All#SparkFun_LG290P
23+
24+
// Adjust these values according to your configuration
25+
int pin_UART1_TX = 14;
26+
int pin_UART1_RX = 13;
27+
int gnss_baud = 460800;
28+
29+
LG290P myGNSS;
30+
HardwareSerial SerialGNSS(1); // Use UART1 on the ESP32
31+
32+
void setup()
33+
{
34+
Serial.begin(115200);
35+
delay(250);
36+
Serial.println();
37+
Serial.println("SparkFun LG290P HAS E6 example");
38+
39+
Serial.println("Initializing device...");
40+
41+
// Increase buffer size to handle high baud rate streams
42+
// SerialGNSS.setRxBufferSize(1024 * 4);
43+
44+
SerialGNSS.begin(gnss_baud, SERIAL_8N1, pin_UART1_RX, pin_UART1_TX);
45+
46+
//myGNSS.enableDebugging(Serial); // Print all debug to Serial
47+
if (myGNSS.begin(SerialGNSS) == false) // Give the serial port over to the library
48+
{
49+
Serial.println("LG290P failed to respond. Check ports and baud rates. Freezing...");
50+
while (true)
51+
;
52+
}
53+
Serial.println("LG290P detected!");
54+
55+
Serial.println("Ensuring ROVER mode");
56+
myGNSS.ensureModeRover();
57+
58+
if (myGNSS.setHighAccuracyService(2, 1) == true) // Enable HAS E6 corrections, use WGS84 datum
59+
Serial.println("HAS E6 corrections enabled.");
60+
else
61+
Serial.println("Failed to enable HAS E6 corrections.");
62+
63+
delay(2000); // Wait before we read the settings
64+
65+
int mode = 0;
66+
int datum = 0;
67+
int timeout = 0;
68+
float horstd = 0;
69+
float verstd = 0;
70+
71+
if (myGNSS.getHighAccuracyService(mode, datum, timeout, horstd, verstd) == true)
72+
{
73+
Serial.print("HAS Service: mode=");
74+
Serial.print(mode);
75+
Serial.print(", datum=");
76+
Serial.print(datum);
77+
Serial.print(", timeout=");
78+
Serial.print(timeout);
79+
Serial.print(", horstd=");
80+
Serial.print(horstd);
81+
Serial.print(", verstd=");
82+
Serial.println(verstd);
83+
}
84+
else
85+
{
86+
Serial.println("Failed to get HAS service info.");
87+
}
88+
}
89+
90+
void loop()
91+
{
92+
myGNSS.update(); // Must be called often to process incoming data
93+
94+
if (myGNSS.getPppSolutionType() == 6 || myGNSS.getPppSolutionType() == 7)
95+
{
96+
if (myGNSS.getPppSolutionType() == 6)
97+
Serial.println("HAS Detected! PPP Solution: PPP Converging");
98+
else if (myGNSS.getPppSolutionType() == 7)
99+
Serial.println("HAS Converged! PPP Solution: PPP Converged");
100+
101+
Serial.printf("DatumID: %d ", myGNSS.getDatumId());
102+
if(myGNSS.getDatumId() == 1)
103+
Serial.println("WGS84");
104+
else if(myGNSS.getDatumId() == 2)
105+
Serial.println("PPP Original");
106+
else if(myGNSS.getDatumId() == 3)
107+
Serial.println("CGCS2000");
108+
109+
Serial.printf("Diff ID: %d ", myGNSS.getPppDifferentialId());
110+
if(myGNSS.getPppDifferentialId() == 9001)
111+
Serial.println("B2b PPP");
112+
else if(myGNSS.getPppDifferentialId() == 9002)
113+
Serial.println("HAS/E6");
114+
115+
Serial.printf("Diff Age: %d seconds\r\n", myGNSS.getPppDifferentialAge());
116+
}
117+
else
118+
{
119+
Serial.printf("PPP Solution Type: %d ", myGNSS.getPppSolutionType());
120+
if(myGNSS.getPppSolutionType() == 1)
121+
Serial.println("Single");
122+
else if(myGNSS.getPppSolutionType() == 2)
123+
Serial.println("Differential (SBAS / DGPS)");
124+
}
125+
126+
int horizontalPositionalAccuracy = myGNSS.get2DError() * 1000; // Convert meters to mm
127+
int threeDPositionalAccuracy = myGNSS.get3DError() * 1000;
128+
129+
Serial.printf("2D Error: %dmm 3D Error: %dmm Sats Used: %d Sats in View: %d\r\n",
130+
horizontalPositionalAccuracy, threeDPositionalAccuracy, myGNSS.getSatellitesUsedCount(), myGNSS.getSatellitesInViewCount());
131+
132+
Serial.println();
133+
134+
// Generally speaking, delaying between checks is a bad idea because the LG290P is outputting so much serial.
135+
// Instead, call myGNSS.update() far more often in order to parse it all.
136+
// If you must do other things and delay a lot, then increase setRxBufferSize to 4096 before beginning the serial port (see above).
137+
delay(1000); // A bad delay to demonstrate the parsing issue
138+
}

keywords.txt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,13 @@ getFirmwareVersion KEYWORD2
157157
getFirmwareVersionMajor KEYWORD2
158158
getFirmwareVersionMinor KEYWORD2
159159

160+
getDatumId KEYWORD2
161+
getPppSolutionType KEYWORD2
162+
getPppDifferentialId KEYWORD2
163+
getPppDifferentialAge KEYWORD2
164+
setHighAccuracyService KEYWORD2
165+
getHighAccuracyService KEYWORD2
166+
160167
#######################################
161168
# Constants (LITERAL1)
162169
#######################################

src/LG290P_structs.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,16 @@ struct PlDomain
7272
void clear() { *this = PlDomain(); }
7373
};
7474

75+
struct PppNavDomain
76+
{
77+
int datumId = 0;
78+
int solType = 0;
79+
int diffId = 0;
80+
int diffAge = 0;
81+
82+
void clear() { *this = PppNavDomain(); }
83+
};
84+
7585
struct SvinStatusDomain
7686
{
7787
int validity = 0;

src/SparkFun_LG290P_GNSS.cpp

Lines changed: 47 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -137,9 +137,9 @@ bool LG290P::begin(HardwareSerial &serialPort, Print *parserDebug, Print *parser
137137
if (ok)
138138
{
139139
debugPrintf("Firmware version is %d.%d %s", firmwareVersionMajor, firmwareVersionMinor, firmwareVersionInt == 0 ? " (Unknown)" : "");
140-
debugPrintf("Starting with %s mode, GGA %d RMC %d EPE %d PVT %d PL %d SVIN %d GSV %d GST %d",
140+
debugPrintf("Starting with %s mode, GGA %d RMC %d EPE %d PVT %d PL %d SVIN %d GSV %d GST %d PPPNAV %d",
141141
devState.mode == BASEMODE ? "BASE" : "ROVER", devState.ggaRate, devState.rmcRate, devState.epeRate,
142-
devState.pvtRate, devState.plRate, devState.svinstatusRate, devState.gsvRate, devState.gstRate);
142+
devState.pvtRate, devState.plRate, devState.svinstatusRate, devState.gsvRate, devState.gstRate, devState.pppnavRate);
143143
}
144144
else
145145
{
@@ -686,6 +686,7 @@ bool LG290P::setMessageRate(const char *msgName, int rate, int msgVer)
686686
else if (str == "PQTMEPE") devState.epeRate = rate;
687687
else if (str == "GSV") devState.gsvRate = rate;
688688
else if (str == "GST") devState.gstRate = rate;
689+
else if (str == "PQTMPPPNAV") devState.pppnavRate = rate;
689690
}
690691
return ret;
691692
}
@@ -711,6 +712,7 @@ bool LG290P::setMessageRateOnPort(const char *msgName, int rate, int portNumber,
711712
else if (str == "PQTMEPE") devState.epeRate = rate;
712713
else if (str == "GSV") devState.gsvRate = rate;
713714
else if (str == "GST") devState.gstRate = rate;
715+
else if (str == "PQTMPPPNAV") devState.pppnavRate = rate;
714716
}
715717
return ret;
716718
}
@@ -789,6 +791,40 @@ bool LG290P::getRtkDifferentialAge(uint16_t &timeout)
789791
return ret;
790792
}
791793

794+
bool LG290P::setHighAccuracyService(int mode, int datum, int timeout, float horstd, float verstd)
795+
{
796+
//CFGPPP does not require reset to take effect
797+
bool ret = true;
798+
if(mode == 0)
799+
ret = sendOkCommand("PQTMCFGPPP", ",W,0"); // $PQTMCFGPPP,W,0,0,0* throws error if other params sent
800+
else
801+
{
802+
char parms[100];
803+
snprintf(parms, sizeof parms, ",W,%d,%d,%d,%0.2f,%0.2f", mode, datum, timeout, horstd, verstd);
804+
ret = sendOkCommand("PQTMCFGPPP", parms);
805+
}
806+
return ret;
807+
}
808+
809+
bool LG290P::getHighAccuracyService(int &mode, int &datum, int &timeout, float &horstd, float &verstd)
810+
{
811+
bool ret = sendCommand("PQTMCFGPPP", ",R");
812+
if (ret)
813+
{
814+
auto packet = getCommandResponse();
815+
ret = packet[1] == "OK";
816+
if (ret)
817+
{
818+
mode = atoi(packet[2].c_str());
819+
datum = atoi(packet[3].c_str());
820+
timeout = atoi(packet[4].c_str());
821+
horstd = atof(packet[5].c_str());
822+
verstd = atof(packet[6].c_str());
823+
}
824+
}
825+
return ret;
826+
}
827+
792828
bool LG290P::scanForMsgsEnabled()
793829
{
794830
bool ok = getMessageRate("GGA", devState.ggaRate);
@@ -797,6 +833,7 @@ bool LG290P::scanForMsgsEnabled()
797833
ok = ok && getMessageRate("PQTMPVT", devState.pvtRate, 1);
798834
ok = ok && getMessageRate("PQTMPL", devState.plRate, 1);
799835
ok = ok && getMessageRate("GSV", devState.gsvRate);
836+
ok = ok && getMessageRate("PQTMPPPNAV", devState.pppnavRate, 1);
800837

801838
// GST is not available on firmware < 4
802839
getMessageRate("GST", devState.gstRate);
@@ -1322,6 +1359,14 @@ void LG290P::nmeaHandler(SEMP_PARSE_STATE *parse)
13221359
plDomain.protectionLevelTime = atof(nmea[14].c_str());
13231360
}
13241361

1362+
else if (id == "PQTMPPPNAV")
1363+
{
1364+
pppNavDomain.datumId = atoi(nmea[9].c_str());
1365+
pppNavDomain.solType = atoi(nmea[11].c_str());
1366+
pppNavDomain.diffId = atoi(nmea[24].c_str());
1367+
pppNavDomain.diffAge = atoi(nmea[25].c_str());
1368+
}
1369+
13251370
#if false
13261371
else if (!ptrLG290P->commandName.empty())
13271372
{

0 commit comments

Comments
 (0)