Skip to content

Commit 73c995f

Browse files
committed
NTRIP Server Survey In Mode sketch
1 parent 2c367d2 commit 73c995f

File tree

3 files changed

+347
-7
lines changed

3 files changed

+347
-7
lines changed
Lines changed: 340 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,340 @@
1+
/*
2+
Use ESP32 WiFi to push RTCM data to RTK2Go (Caster) as a Server, using Survey In to calculate base location
3+
By: SparkFun Electronics / Nathan Seidle and Mikal Hart
4+
Date: November 10, 2024
5+
License: MIT. See license file for more information.
6+
7+
This example shows how to gather RTCM data over Serial and push it to a casting service over WiFi.
8+
The base location is calculated using the "Survey In" technique.
9+
10+
Here, the Arduino/ESP32 is acting as a 'server' to a 'caster'. In this case we will
11+
use RTK2Go.com as our caster because it is free. A rover (car, surveyor stick, etc)
12+
can then connect to RTK2Go as a 'client' and get the RTCM data it needs.
13+
14+
You will need to register your mountpoint here: http://www.rtk2go.com/new-reservation/
15+
(They'll probably block the credentials we include in this example)
16+
17+
To see if your mountpoint is active go here: http://rtk2go.com:2101/
18+
19+
This is a proof of concept. Serving RTCM to a caster over WiFi is useful when you need to
20+
set up a high-precision base station.
21+
22+
Feel like supporting open source hardware?
23+
Buy a board from SparkFun!
24+
ZED-F9P RTK2: https://www.sparkfun.com/products/16481
25+
RTK Surveyor: https://www.sparkfun.com/products/17369
26+
27+
Hardware Connections:
28+
Connect the LG290P to an ESP32 Thing Plus's UART.
29+
Open the serial monitor at 115200 baud to see the output
30+
*/
31+
32+
#include <WiFi.h>
33+
#include "secrets.h"
34+
#include <SparkFun_LG290P_GNSS.h> // Click here to get the library: http://librarymanager/All#SparkFun_LG290P
35+
36+
WiFiClient ntripCaster;
37+
38+
// Adjust these values according to your configuration
39+
int pin_UART1_TX = 14;
40+
int pin_UART1_RX = 13;
41+
int gnss_baud = 460800;
42+
const int maxTimeBeforeHangup_ms = 10000; // If we fail to get a complete RTCM frame after 10s, then disconnect from caster
43+
44+
LG290P myGNSS;
45+
HardwareSerial SerialGNSS(1); // Use UART1 on the ESP32
46+
47+
// Global Variables
48+
//=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
49+
long lastSentRTCM_ms = 0; // Time of last data pushed to socket
50+
uint32_t serverBytesSent = 0; // Just a running total
51+
long lastReport_ms = 0; // Time of last report of bytes sent
52+
double ecefX, ecefY, ecefZ; // ECEF coordinates
53+
//=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
54+
55+
void setup()
56+
{
57+
Serial.begin(115200); // You may need to increase this for high navigation rates!
58+
delay(250);
59+
Serial.println();
60+
Serial.println("SparkFun NTRIP Server Survey In example");
61+
Serial.println("Initializing device...");
62+
63+
// We must start the serial port before using it in the library
64+
// Increase buffer size to handle high baud rate streams
65+
SerialGNSS.setRxBufferSize(4096);
66+
SerialGNSS.begin(gnss_baud, SERIAL_8N1, pin_UART1_RX, pin_UART1_TX);
67+
68+
// myGNSS.enableDebugging(Serial); // Print all debug to Serial
69+
if (myGNSS.begin(SerialGNSS) == false) // Give the serial port over to the library
70+
{
71+
Serial.println("LG290P failed to respond. Check ports and baud rates. Freezing...");
72+
while (true)
73+
;
74+
}
75+
Serial.println("LG290P detected!");
76+
77+
Serial.print("Connecting to local WiFi");
78+
WiFi.begin(ssid, password);
79+
while (WiFi.status() != WL_CONNECTED)
80+
{
81+
delay(500);
82+
Serial.print(".");
83+
}
84+
85+
Serial.print("\nWiFi connected with IP: ");
86+
Serial.println(WiFi.localIP());
87+
88+
bool response = myGNSS.rtcmSubscribeAll(rtcmCallback);
89+
response = response && myGNSS.setModeBase(false);
90+
if (response)
91+
Serial.println(F("Mode set successfully"));
92+
else
93+
Serial.println(F("Mode set failed!"));
94+
95+
// Turn off unneeded NMEA sentences
96+
if (response)
97+
{
98+
response =
99+
myGNSS.setMessageRate("GLL", 0) &&
100+
myGNSS.setMessageRate("GSA", 0) &&
101+
myGNSS.setMessageRate("GSV", 0) &&
102+
myGNSS.setMessageRate("GGA", 0) &&
103+
myGNSS.setMessageRate("RMC", 0);
104+
105+
if (response)
106+
Serial.println(F("NMEA messages were configured successfully"));
107+
else
108+
Serial.println(F("NMEA message configuration failed!"));
109+
}
110+
111+
// Make sure necessary RTCM sentences are enabled
112+
if (response)
113+
{
114+
response =
115+
myGNSS.setMessageRate("RTCM3-1005", 1) &&
116+
myGNSS.setMessageRate("RTCM3-107X", 1, 0) &&
117+
myGNSS.setMessageRate("RTCM3-108X", 1, 0) &&
118+
myGNSS.setMessageRate("RTCM3-109X", 1, 0) &&
119+
myGNSS.setMessageRate("RTCM3-112X", 1, 0);
120+
121+
if (response)
122+
Serial.println(F("RTCM messages enabled"));
123+
else
124+
Serial.println(F("RTCM failed to enable. Are you sure you have an LG290P?"));
125+
}
126+
127+
if (response)
128+
{
129+
// -1280208.308,-4716803.847,4086665.811 is SparkFun HQ in ECEF coordinates so...
130+
// Note: If you leave these coordinates in place and setup your antenna *not* at SparkFun, your receiver
131+
// will be very confused and fail to generate correction data because, well, you aren't at SparkFun...
132+
// See this tutorial on getting PPP coordinates: https://learn.sparkfun.com/tutorials/how-to-build-a-diy-gnss-reference-station/all
133+
//
134+
Serial.print("Setting 'survey in' mode... "); Serial.flush();
135+
response = myGNSS.setSurveyInMode(60);
136+
if (response)
137+
Serial.println(F("'survey in' mode set."));
138+
else
139+
Serial.println(F("failed to set 'survey in' mode."));
140+
}
141+
142+
if (response)
143+
{
144+
int v = -1, c = -1;
145+
Serial.println("Waiting for status = VALID");
146+
while (true)
147+
{
148+
myGNSS.update();
149+
int validity = myGNSS.getSurveyInStatus();
150+
int count = myGNSS.getSurveyInObservations();
151+
if (v != validity || c != count)
152+
{
153+
Serial.printf("Status: %s, Count: %d/60\r\n", validity == 0 ? "INVALID" : validity == 1 ? "IN-PROGRESS" : "VALID", count);
154+
c = count;
155+
v = validity;
156+
if (validity >= 2)
157+
break;
158+
}
159+
}
160+
}
161+
162+
163+
if (response)
164+
{
165+
Serial.println(F("Module configuration complete"));
166+
Serial.println(F("Press any key to start NTRIP Server."));
167+
}
168+
else
169+
{
170+
Serial.println(F("Freezing process..."));
171+
while (true)
172+
;
173+
}
174+
}
175+
176+
void loop()
177+
{
178+
if (Serial.available())
179+
{
180+
beginServing();
181+
while (Serial.available()) Serial.read(); // Empty buffer of any newline chars
182+
Serial.println(F("Press any key to start NTRIP Server."));
183+
}
184+
}
185+
186+
void beginServing()
187+
{
188+
Serial.println("Feeding RTCM to caster. Press any key to stop");
189+
while (Serial.available()) // Flush the serial buffer
190+
Serial.read();
191+
192+
if (!ntripCaster.connected())
193+
{
194+
Serial.printf("Opening HTTP connection to %s\r\n", casterHost);
195+
196+
if (!ntripCaster.connect(casterHost, casterPort)) // Attempt connection
197+
{
198+
Serial.println(F("Connection to caster failed"));
199+
return;
200+
}
201+
}
202+
Serial.printf("Connected to %s:%d\r\n", casterHost, casterPort);
203+
Serial.printf("Feeding NTRIP Data from mount point %s\r\n", mountPoint);
204+
205+
std::string request = std::string("SOURCE ") + mountPointPW + " /" + mountPoint + "\r\nSource-Agent: NTRIP SparkFun LG290P Server v1.0\r\n\r\n";
206+
Serial.printf("Sending request: '%s'\r\n", request.c_str());
207+
ntripCaster.write(request.c_str(), request.length());
208+
209+
// Wait for response
210+
unsigned long timeout = millis();
211+
while (ntripCaster.available() == 0)
212+
{
213+
if (millis() - timeout > 5000)
214+
{
215+
Serial.println(F("Caster timed out!"));
216+
ntripCaster.stop();
217+
return;
218+
}
219+
myGNSS.update();
220+
}
221+
222+
// Check reply
223+
char response[512] = {0};
224+
bool connectionSuccess = false;
225+
size_t bytesToRead = std::min(sizeof response - 1, (size_t)ntripCaster.available());
226+
size_t bytesRead = ntripCaster.readBytes(response, bytesToRead);
227+
if (strstr(response, "200") != nullptr) // Look for 'ICY 200 OK'
228+
connectionSuccess = true;
229+
else if (strstr(response, "401") != nullptr) // Look for '401 Unauthorized'
230+
{
231+
Serial.println(F("Hey - your credentials look bad! Check your caster username and password."));
232+
connectionSuccess = false;
233+
}
234+
235+
Serial.printf("Caster responsed with %d bytes\r\n", bytesRead);
236+
Serial.printf("Caster responded with: '%s'\r\n", response);
237+
238+
if (!connectionSuccess)
239+
{
240+
Serial.printf("Failed to connect to %s: %s\r\n", casterHost, response);
241+
return;
242+
}
243+
Serial.printf("Connected to %s\r\n", casterHost);
244+
lastReport_ms = lastSentRTCM_ms = millis(); // Reset timeout
245+
246+
// This is the main sending loop. (Sending itself happens in RTCM callback function below.)
247+
while (ntripCaster.connected() && !Serial.available())
248+
{
249+
myGNSS.update(); // Process bytes as they arrive from LG290P
250+
251+
// Terminate HTTP connection if we don't have new data for 10s
252+
// RTK2Go will ban your IP address if you abuse it. See http://www.rtk2go.com/how-to-get-your-ip-banned/
253+
// So let's not leave the connection open/hanging without data
254+
if (millis() - lastSentRTCM_ms > maxTimeBeforeHangup_ms)
255+
{
256+
Serial.println("RTCM timeout. Disconnecting...");
257+
ntripCaster.stop();
258+
return;
259+
}
260+
261+
// Report some statistics every second
262+
if (millis() - lastReport_ms >= 1000)
263+
{
264+
lastReport_ms = millis();
265+
displayData();
266+
}
267+
}
268+
269+
if (Serial.available())
270+
{
271+
Serial.println(F("User pressed a key"));
272+
Serial.println(F("Disconnecting..."));
273+
}
274+
else
275+
{
276+
Serial.println(F("Lost connection..."));
277+
}
278+
ntripCaster.stop();
279+
280+
while (Serial.available()) // Flush any residual data in serial buffer
281+
Serial.read();
282+
}
283+
void displayData()
284+
{
285+
// Every 20th line draw the helpful header
286+
static int linecount = 0;
287+
if (linecount++ % 20 == 0)
288+
{
289+
displayHeader();
290+
}
291+
292+
Serial.printf("%02d:%02d:%02d %-12.3f %-12.3f %-12.3f %4s %-6d %-6d %-6d %-6d %-6d %-10d\r\n",
293+
myGNSS.getHour(), myGNSS.getMinute(), myGNSS.getSecond(),
294+
ecefX, ecefY, ecefZ, "",
295+
myGNSS.getRtcmCount(1005), myGNSS.getRtcmCount(1074), myGNSS.getRtcmCount(1084), myGNSS.getRtcmCount(1094), myGNSS.getRtcmCount(1124),
296+
serverBytesSent);
297+
}
298+
299+
void displayHeader()
300+
{
301+
const char *headings[] = { "Time", "ECEF-X", "ECEF-Y", "ECEF-Z", "RTCM", "1005", "1074", "1084", "1094", "1124", "Bytes-Sent"};
302+
int widths[] = { 8, 12, 12, 12, 4, 6, 6, 6, 6, 6, 10 };
303+
int items = sizeof widths / sizeof widths[0];
304+
Serial.println();
305+
306+
// Header
307+
for (int i=0; i<items; ++i)
308+
{
309+
char buf[10]; sprintf(buf, "%%-%ds ", widths[i]);
310+
Serial.printf(buf, headings[i]);
311+
}
312+
Serial.println();
313+
314+
// Dashes
315+
for (int i=0; i<items; ++i)
316+
{
317+
std::string dashes(widths[i], '-');
318+
Serial.printf("%s%s", dashes.c_str(), i == items - 1 ? "" : "-");
319+
}
320+
Serial.println();
321+
}
322+
323+
324+
void rtcmCallback(RtcmPacket &packet)
325+
{
326+
if (ntripCaster.connected())
327+
{
328+
// Serial.printf("Sending RTCM packet type %d...\r\n", packet.type);
329+
ntripCaster.write(packet.buffer, packet.bufferlen);
330+
lastSentRTCM_ms = millis();
331+
serverBytesSent += packet.bufferlen;
332+
if (packet.type == 1005)
333+
{
334+
ecefX = packet.getEcefX();
335+
ecefY = packet.getEcefY();
336+
ecefZ = packet.getEcefZ();
337+
}
338+
}
339+
}
340+

examples/9. NTRIP/NTRIP.Server/NTRIP.Server.ino

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -94,11 +94,11 @@ void setup()
9494
if (response)
9595
{
9696
response =
97-
myGNSS.setMessageRate("GLL", 0) &&
98-
myGNSS.setMessageRate("GSA", 0) &&
99-
myGNSS.setMessageRate("GSV", 0) &&
100-
myGNSS.setMessageRate("GGA", 0) &&
101-
myGNSS.setMessageRate("RMC", 0);
97+
myGNSS.setMessageRate("GLL", 0) &&
98+
myGNSS.setMessageRate("GSA", 0) &&
99+
myGNSS.setMessageRate("GSV", 0) &&
100+
myGNSS.setMessageRate("GGA", 0) &&
101+
myGNSS.setMessageRate("RMC", 0);
102102

103103
if (response)
104104
Serial.println(F("NMEA messages were configured successfully"));

src/SparkFun_LG290P_GNSS.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -908,13 +908,13 @@ class LG290P
908908
double getProtLevelTime() { ensurePlEnabled(); return plDomain.protectionLevelTime; }
909909

910910
/**
911-
* @brief Returns the Validity field from PQTMSVINSTATUS
911+
* @brief Returns the Validity field from PQTMSVINSTATUS (0=Invalid, 1=In-progress, 2=Valid)
912912
* @return Validity
913913
*/
914914
int getSurveyInStatus() { ensureSvinStatusEnabled(); return svinStatusDomain.validity; }
915915

916916
/**
917-
* @brief Returns the Observations field from PQTMSVINSTATUS
917+
* @brief Returns the Observations field from PQTMSVINSTATUS (count between 0 and CfgDur)
918918
* @return Observations
919919
*/
920920
int getSurveyInObservations() { ensureSvinStatusEnabled(); return svinStatusDomain.observations; }

0 commit comments

Comments
 (0)