Skip to content

Commit 20ec13e

Browse files
committed
NTRIP Server example
1 parent 1e23197 commit 20ec13e

File tree

4 files changed

+337
-5
lines changed

4 files changed

+337
-5
lines changed

examples/9. NTRIP/NTRIP.Client/NTRIP.Client.ino

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,9 @@ void setup()
8888
Serial.print(F("WiFi connected with IP: "));
8989
Serial.println(WiFi.localIP());
9090

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

src/LG290P_structs.h

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -265,8 +265,15 @@ struct RtcmPacket
265265
uint16_t payloadLen;
266266
uint16_t type;
267267

268-
int64_t extract_38bit_signed(int bit_offset);
268+
// Extract ECEF components from RTCM 1005 packets
269+
double getEcefX() { return type == 1005 ? extract_38bit_signed(34) / 10000.0 : 0.0; }
270+
double getEcefY() { return type == 1005 ? extract_38bit_signed(74) / 10000.0 : 0.0; }
271+
double getEcefZ() { return type == 1005 ? extract_38bit_signed(114) / 10000.0 : 0.0; }
272+
269273
static bool FromBuffer(uint8_t *buffer, size_t bufferLen, RtcmPacket &result);
274+
275+
private:
276+
int64_t extract_38bit_signed(int bit_offset);
270277
};
271278

272279

src/SparkFun_LG290P_GNSS.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1227,9 +1227,9 @@ void LG290P::rtcmHandler(SEMP_PARSE_STATE *parse)
12271227

12281228
if (packet.type == 1005)
12291229
{
1230-
rtcmDomain.ecefX = packet.extract_38bit_signed(34) / 10000.0;
1231-
rtcmDomain.ecefY = packet.extract_38bit_signed(74) / 10000.0;
1232-
rtcmDomain.ecefZ = packet.extract_38bit_signed(114) / 10000.0;
1230+
rtcmDomain.ecefX = packet.getEcefX();
1231+
rtcmDomain.ecefY = packet.getEcefY();
1232+
rtcmDomain.ecefZ = packet.getEcefZ();
12331233
lastUpdateEcef = millis();
12341234
}
12351235

0 commit comments

Comments
 (0)