Skip to content

Commit 8778f70

Browse files
committed
Add NTRIP Client example
1 parent b8a7519 commit 8778f70

File tree

2 files changed

+278
-0
lines changed

2 files changed

+278
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,261 @@
1+
/*
2+
Use ESP32 WiFi to get RTCM data from RTK2Go (caster) as a Client
3+
By: SparkFun Electronics / Nathan Seidle
4+
Date: November 18th, 2021
5+
License: MIT. See license file for more information but you can
6+
basically do whatever you want with this code.
7+
8+
This example shows how to obtain RTCM data from a NTRIP Caster over WiFi
9+
and push it over I2C to a ZED-F9x.
10+
It's confusing, but the Arduino is acting as a 'client' to a 'caster'. In this case we will
11+
use RTK2Go.com as our caster because it is free. See the NTRIPServer example to see how
12+
to push RTCM data to the caster.
13+
14+
You will need to have a valid mountpoint available. To see available mountpoints go here: http://rtk2go.com:2101/
15+
16+
This is a proof of concept to show how to connect to a caster via HTTP. Using WiFi for a rover
17+
is generally a bad idea because of limited WiFi range in the field.
18+
19+
Feel like supporting open source hardware?
20+
Buy a board from SparkFun!
21+
ZED-F9P RTK2: https://www.sparkfun.com/products/16481
22+
RTK Surveyor: https://www.sparkfun.com/products/18443
23+
RTK Express: https://www.sparkfun.com/products/18442
24+
25+
Hardware Connections:
26+
Plug a Qwiic cable into the GNSS and a ESP32 Thing Plus
27+
If you don't have a platform with a Qwiic connection use the SparkFun Qwiic Breadboard Jumper (https://www.sparkfun.com/products/14425)
28+
Open the serial monitor at 115200 baud to see the output
29+
*/
30+
#include <WiFi.h>
31+
#include "secrets.h"
32+
33+
#include <SparkFun_u-blox_GNSS_Arduino_Library.h> //http://librarymanager/All#SparkFun_u-blox_GNSS
34+
SFE_UBLOX_GNSS myGNSS;
35+
36+
//The ESP32 core has a built in base64 library but not every platform does
37+
//We'll use an external lib if necessary.
38+
#if defined(ARDUINO_ARCH_ESP32)
39+
#include "base64.h" //Built-in ESP32 library
40+
#else
41+
#include <Base64.h> //nfriendly library from https://github.com/adamvr/arduino-base64, will work with any platform
42+
#endif
43+
44+
//Global variables
45+
//=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
46+
long lastReceivedRTCM_ms = 0; //5 RTCM messages take approximately ~300ms to arrive at 115200bps
47+
int maxTimeBeforeHangup_ms = 10000; //If we fail to get a complete RTCM frame after 10s, then disconnect from caster
48+
//=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
49+
50+
void setup()
51+
{
52+
Serial.begin(115200);
53+
Serial.println("NTRIP testing");
54+
55+
Wire.begin(); //Start I2C
56+
57+
if (myGNSS.begin() == false) //Connect to the Ublox module using Wire port
58+
{
59+
Serial.println(F("u-blox GPS not detected at default I2C address. Please check wiring. Freezing."));
60+
while (1);
61+
}
62+
Serial.println(F("u-blox module connected"));
63+
64+
myGNSS.setI2COutput(COM_TYPE_UBX); //Turn off NMEA noise
65+
myGNSS.setPortInput(COM_PORT_I2C, COM_TYPE_UBX | COM_TYPE_NMEA | COM_TYPE_RTCM3); //Be sure RTCM3 input is enabled. UBX + RTCM3 is not a valid state.
66+
67+
myGNSS.setNavigationFrequency(1); //Set output in Hz.
68+
69+
Serial.print("Connecting to local WiFi");
70+
WiFi.begin(ssid, password);
71+
while (WiFi.status() != WL_CONNECTED) {
72+
delay(500);
73+
Serial.print(".");
74+
}
75+
Serial.println();
76+
77+
Serial.print("WiFi connected with IP: ");
78+
Serial.println(WiFi.localIP());
79+
80+
while (Serial.available()) Serial.read();
81+
}
82+
83+
void loop()
84+
{
85+
if (Serial.available()) beginClient();
86+
87+
Serial.println(F("Press any key to start NTRIP Client."));
88+
89+
delay(1000);
90+
}
91+
92+
//Connect to NTRIP Caster, receive RTCM, and push to ZED module over I2C
93+
void beginClient()
94+
{
95+
WiFiClient ntripClient;
96+
long rtcmCount = 0;
97+
98+
Serial.println("Subscribing to Caster. Press key to stop");
99+
delay(10); //Wait for any serial to arrive
100+
while (Serial.available()) Serial.read(); //Flush
101+
102+
while (Serial.available() == 0)
103+
{
104+
//Connect if we are not already
105+
if (ntripClient.connected() == false)
106+
{
107+
Serial.print("Opening socket to");
108+
Serial.println(casterHost);
109+
110+
if (ntripClient.connect(casterHost, casterPort) == false) //Attempt connection
111+
{
112+
Serial.println("Connection to caster failed");
113+
}
114+
else
115+
{
116+
Serial.print("Connected to ");
117+
Serial.print(casterHost);
118+
Serial.print(": ");
119+
Serial.println(casterPort);
120+
121+
Serial.print("Requesting NTRIP Data from mount point ");
122+
Serial.println(mountPoint);
123+
124+
const int SERVER_BUFFER_SIZE = 512;
125+
char serverRequest[SERVER_BUFFER_SIZE];
126+
127+
snprintf(serverRequest, SERVER_BUFFER_SIZE, "GET /%s HTTP/1.0\r\nUser-Agent: SparkFun u-blox NTRIPClient v1.0\r\n",
128+
mountPoint);
129+
130+
char credentials[512];
131+
if (strlen(casterUser) == 0)
132+
{
133+
strncpy(credentials, "Accept: */*\r\nConnection: close\r\n", sizeof(credentials));
134+
}
135+
else
136+
{
137+
//Pass base64 encoded user:pw
138+
char userCredentials[sizeof(casterUser) + sizeof(casterUserPW) + 1]; //The ':' takes up a spot
139+
snprintf(userCredentials, sizeof(userCredentials), "%s:%s", casterUser, casterUserPW);
140+
141+
Serial.print("Sending credentials: ");
142+
Serial.println(userCredentials);
143+
144+
#if defined(ARDUINO_ARCH_ESP32)
145+
//Encode with ESP32 built-in library
146+
base64 b;
147+
String strEncodedCredentials = b.encode(userCredentials);
148+
char encodedCredentials[strEncodedCredentials.length() + 1];
149+
strEncodedCredentials.toCharArray(encodedCredentials, sizeof(encodedCredentials)); //Convert String to char array
150+
snprintf(credentials, sizeof(credentials), "Authorization: Basic %s\r\n", encodedCredentials);
151+
#else
152+
//Encode with nfriendly library
153+
int encodedLen = base64_enc_len(strlen(userCredentials));
154+
char encodedCredentials[encodedLen]; //Create array large enough to house encoded data
155+
base64_encode(encodedCredentials, userCredentials, strlen(userCredentials)); //Note: Input array is consumed
156+
#endif
157+
}
158+
strncat(serverRequest, credentials, SERVER_BUFFER_SIZE);
159+
strncat(serverRequest, "\r\n", SERVER_BUFFER_SIZE);
160+
161+
Serial.print("serverRequest size: ");
162+
Serial.print(strlen(serverRequest));
163+
Serial.print(" of ");
164+
Serial.print(sizeof(serverRequest));
165+
Serial.println(" bytes available");
166+
167+
Serial.println("Sending server request:");
168+
Serial.println(serverRequest);
169+
ntripClient.write(serverRequest, strlen(serverRequest));
170+
171+
//Wait for response
172+
unsigned long timeout = millis();
173+
while (ntripClient.available() == 0)
174+
{
175+
if (millis() - timeout > 5000)
176+
{
177+
Serial.println("Mountpoint timed out!");
178+
ntripClient.stop();
179+
return;
180+
}
181+
delay(10);
182+
}
183+
184+
//Check reply
185+
bool connectionSuccess = false;
186+
char response[512];
187+
int responseSpot = 0;
188+
while (ntripClient.available())
189+
{
190+
if (responseSpot == sizeof(response) - 1) break;
191+
192+
response[responseSpot++] = ntripClient.read();
193+
if (strstr(response, "200") > 0) //Look for 'ICY 200 OK'
194+
connectionSuccess = true;
195+
if (strstr(response, "401") > 0) //Look for '401 Unauthorized'
196+
{
197+
Serial.println("Hey - your credentials look bad! Check you caster username and password.");
198+
connectionSuccess = false;
199+
}
200+
}
201+
response[responseSpot] = '\0';
202+
203+
if (connectionSuccess == false)
204+
{
205+
Serial.print("Failed to connect to ");
206+
Serial.print(casterHost);
207+
Serial.print(": ");
208+
Serial.println(response);
209+
delay(5000); //Don't spam with lots of connection attempts
210+
}
211+
else
212+
{
213+
Serial.print("Connected to ");
214+
Serial.println(casterHost);
215+
lastReceivedRTCM_ms = millis(); //Reset timeout
216+
}
217+
} //End attempt to connect
218+
} //End connected == false
219+
220+
if (ntripClient.connected() == true)
221+
{
222+
uint8_t rtcmData[512 * 4]; //Most incoming data is around 500 bytes but may be larger
223+
rtcmCount = 0;
224+
225+
//Print any available RTCM data
226+
while (ntripClient.available())
227+
{
228+
//Serial.write(ntripClient.read()); //Pipe to serial port is fine but beware, it's a lot of binary data
229+
rtcmData[rtcmCount++] = ntripClient.read();
230+
if (rtcmCount == sizeof(rtcmData)) break;
231+
}
232+
233+
if (rtcmCount > 0)
234+
{
235+
lastReceivedRTCM_ms = millis();
236+
237+
//Push RTCM to GNSS module over I2C
238+
myGNSS.pushRawData(rtcmData, rtcmCount, false);
239+
Serial.print("RTCM pushed to ZED: ");
240+
Serial.println(rtcmCount);
241+
}
242+
}
243+
244+
//Close socket if we don't have new data for 10s
245+
if (millis() - lastReceivedRTCM_ms > maxTimeBeforeHangup_ms)
246+
{
247+
Serial.println("RTCM timeout. Disconnecting...");
248+
if (ntripClient.connected() == true)
249+
ntripClient.stop();
250+
return;
251+
}
252+
253+
delay(10);
254+
}
255+
256+
Serial.println("User pressed a key");
257+
Serial.println("Disconnecting...");
258+
ntripClient.stop();
259+
260+
while (Serial.available()) Serial.read(); //Empty buffer of any newline chars
261+
}

Diff for: examples/ZED-F9P/Example15_NTRIPClient/secrets.h

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
//Your WiFi credentials
2+
const char ssid[] = "TRex";
3+
const char password[] = "hasBigTeeth";
4+
5+
//RTK2Go works well and is free
6+
const char casterHost[] = "rtk2go.com";
7+
const uint16_t casterPort = 2101;
8+
const char casterUser[] = "[email protected]"; //User must provide their own email address to use RTK2Go
9+
const char casterUserPW[] = "";
10+
const char mountPoint[] = "bldr_SparkFun1"; //The mount point you want to get data from
11+
12+
//Emlid Caster also works well and is free
13+
//const char casterHost[] = "caster.emlid.com";
14+
//const uint16_t casterPort = 2101;
15+
//const char casterUser[] = "u99696"; //User name and pw must be obtained through their web portal
16+
//const char casterUserPW[] = "466zez";
17+
//const char mountPoint[] = "MP1979"; //The mount point you want to get data from

0 commit comments

Comments
 (0)