Merge pull request #82 from sparkfun/NTRIP_Client
Add NTRIP Client example
This commit is contained in:
@@ -0,0 +1,261 @@
|
||||
/*
|
||||
Use ESP32 WiFi to get RTCM data from RTK2Go (caster) as a Client
|
||||
By: SparkFun Electronics / Nathan Seidle
|
||||
Date: November 18th, 2021
|
||||
License: MIT. See license file for more information but you can
|
||||
basically do whatever you want with this code.
|
||||
|
||||
This example shows how to obtain RTCM data from a NTRIP Caster over WiFi
|
||||
and push it over I2C to a ZED-F9x.
|
||||
It's confusing, but the Arduino is acting as a 'client' to a 'caster'. In this case we will
|
||||
use RTK2Go.com as our caster because it is free. See the NTRIPServer example to see how
|
||||
to push RTCM data to the caster.
|
||||
|
||||
You will need to have a valid mountpoint available. To see available mountpoints go here: http://rtk2go.com:2101/
|
||||
|
||||
This is a proof of concept to show how to connect to a caster via HTTP. Using WiFi for a rover
|
||||
is generally a bad idea because of limited WiFi range in the field.
|
||||
|
||||
Feel like supporting open source hardware?
|
||||
Buy a board from SparkFun!
|
||||
ZED-F9P RTK2: https://www.sparkfun.com/products/16481
|
||||
RTK Surveyor: https://www.sparkfun.com/products/18443
|
||||
RTK Express: https://www.sparkfun.com/products/18442
|
||||
|
||||
Hardware Connections:
|
||||
Plug a Qwiic cable into the GNSS and a ESP32 Thing Plus
|
||||
If you don't have a platform with a Qwiic connection use the SparkFun Qwiic Breadboard Jumper (https://www.sparkfun.com/products/14425)
|
||||
Open the serial monitor at 115200 baud to see the output
|
||||
*/
|
||||
#include <WiFi.h>
|
||||
#include "secrets.h"
|
||||
|
||||
#include <SparkFun_u-blox_GNSS_Arduino_Library.h> //http://librarymanager/All#SparkFun_u-blox_GNSS
|
||||
SFE_UBLOX_GNSS myGNSS;
|
||||
|
||||
//The ESP32 core has a built in base64 library but not every platform does
|
||||
//We'll use an external lib if necessary.
|
||||
#if defined(ARDUINO_ARCH_ESP32)
|
||||
#include "base64.h" //Built-in ESP32 library
|
||||
#else
|
||||
#include <Base64.h> //nfriendly library from https://github.com/adamvr/arduino-base64, will work with any platform
|
||||
#endif
|
||||
|
||||
//Global variables
|
||||
//=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
|
||||
long lastReceivedRTCM_ms = 0; //5 RTCM messages take approximately ~300ms to arrive at 115200bps
|
||||
int maxTimeBeforeHangup_ms = 10000; //If we fail to get a complete RTCM frame after 10s, then disconnect from caster
|
||||
//=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
|
||||
|
||||
void setup()
|
||||
{
|
||||
Serial.begin(115200);
|
||||
Serial.println("NTRIP testing");
|
||||
|
||||
Wire.begin(); //Start I2C
|
||||
|
||||
if (myGNSS.begin() == false) //Connect to the Ublox module using Wire port
|
||||
{
|
||||
Serial.println(F("u-blox GPS not detected at default I2C address. Please check wiring. Freezing."));
|
||||
while (1);
|
||||
}
|
||||
Serial.println(F("u-blox module connected"));
|
||||
|
||||
myGNSS.setI2COutput(COM_TYPE_UBX); //Turn off NMEA noise
|
||||
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.
|
||||
|
||||
myGNSS.setNavigationFrequency(1); //Set output in Hz.
|
||||
|
||||
Serial.print("Connecting to local WiFi");
|
||||
WiFi.begin(ssid, password);
|
||||
while (WiFi.status() != WL_CONNECTED) {
|
||||
delay(500);
|
||||
Serial.print(".");
|
||||
}
|
||||
Serial.println();
|
||||
|
||||
Serial.print("WiFi connected with IP: ");
|
||||
Serial.println(WiFi.localIP());
|
||||
|
||||
while (Serial.available()) Serial.read();
|
||||
}
|
||||
|
||||
void loop()
|
||||
{
|
||||
if (Serial.available()) beginClient();
|
||||
|
||||
Serial.println(F("Press any key to start NTRIP Client."));
|
||||
|
||||
delay(1000);
|
||||
}
|
||||
|
||||
//Connect to NTRIP Caster, receive RTCM, and push to ZED module over I2C
|
||||
void beginClient()
|
||||
{
|
||||
WiFiClient ntripClient;
|
||||
long rtcmCount = 0;
|
||||
|
||||
Serial.println("Subscribing to Caster. Press key to stop");
|
||||
delay(10); //Wait for any serial to arrive
|
||||
while (Serial.available()) Serial.read(); //Flush
|
||||
|
||||
while (Serial.available() == 0)
|
||||
{
|
||||
//Connect if we are not already
|
||||
if (ntripClient.connected() == false)
|
||||
{
|
||||
Serial.print("Opening socket to");
|
||||
Serial.println(casterHost);
|
||||
|
||||
if (ntripClient.connect(casterHost, casterPort) == false) //Attempt connection
|
||||
{
|
||||
Serial.println("Connection to caster failed");
|
||||
}
|
||||
else
|
||||
{
|
||||
Serial.print("Connected to ");
|
||||
Serial.print(casterHost);
|
||||
Serial.print(": ");
|
||||
Serial.println(casterPort);
|
||||
|
||||
Serial.print("Requesting NTRIP Data from mount point ");
|
||||
Serial.println(mountPoint);
|
||||
|
||||
const int SERVER_BUFFER_SIZE = 512;
|
||||
char serverRequest[SERVER_BUFFER_SIZE];
|
||||
|
||||
snprintf(serverRequest, SERVER_BUFFER_SIZE, "GET /%s HTTP/1.0\r\nUser-Agent: SparkFun u-blox NTRIPClient v1.0\r\n",
|
||||
mountPoint);
|
||||
|
||||
char credentials[512];
|
||||
if (strlen(casterUser) == 0)
|
||||
{
|
||||
strncpy(credentials, "Accept: */*\r\nConnection: close\r\n", sizeof(credentials));
|
||||
}
|
||||
else
|
||||
{
|
||||
//Pass base64 encoded user:pw
|
||||
char userCredentials[sizeof(casterUser) + sizeof(casterUserPW) + 1]; //The ':' takes up a spot
|
||||
snprintf(userCredentials, sizeof(userCredentials), "%s:%s", casterUser, casterUserPW);
|
||||
|
||||
Serial.print("Sending credentials: ");
|
||||
Serial.println(userCredentials);
|
||||
|
||||
#if defined(ARDUINO_ARCH_ESP32)
|
||||
//Encode with ESP32 built-in library
|
||||
base64 b;
|
||||
String strEncodedCredentials = b.encode(userCredentials);
|
||||
char encodedCredentials[strEncodedCredentials.length() + 1];
|
||||
strEncodedCredentials.toCharArray(encodedCredentials, sizeof(encodedCredentials)); //Convert String to char array
|
||||
snprintf(credentials, sizeof(credentials), "Authorization: Basic %s\r\n", encodedCredentials);
|
||||
#else
|
||||
//Encode with nfriendly library
|
||||
int encodedLen = base64_enc_len(strlen(userCredentials));
|
||||
char encodedCredentials[encodedLen]; //Create array large enough to house encoded data
|
||||
base64_encode(encodedCredentials, userCredentials, strlen(userCredentials)); //Note: Input array is consumed
|
||||
#endif
|
||||
}
|
||||
strncat(serverRequest, credentials, SERVER_BUFFER_SIZE);
|
||||
strncat(serverRequest, "\r\n", SERVER_BUFFER_SIZE);
|
||||
|
||||
Serial.print("serverRequest size: ");
|
||||
Serial.print(strlen(serverRequest));
|
||||
Serial.print(" of ");
|
||||
Serial.print(sizeof(serverRequest));
|
||||
Serial.println(" bytes available");
|
||||
|
||||
Serial.println("Sending server request:");
|
||||
Serial.println(serverRequest);
|
||||
ntripClient.write(serverRequest, strlen(serverRequest));
|
||||
|
||||
//Wait for response
|
||||
unsigned long timeout = millis();
|
||||
while (ntripClient.available() == 0)
|
||||
{
|
||||
if (millis() - timeout > 5000)
|
||||
{
|
||||
Serial.println("Mountpoint timed out!");
|
||||
ntripClient.stop();
|
||||
return;
|
||||
}
|
||||
delay(10);
|
||||
}
|
||||
|
||||
//Check reply
|
||||
bool connectionSuccess = false;
|
||||
char response[512];
|
||||
int responseSpot = 0;
|
||||
while (ntripClient.available())
|
||||
{
|
||||
if (responseSpot == sizeof(response) - 1) break;
|
||||
|
||||
response[responseSpot++] = ntripClient.read();
|
||||
if (strstr(response, "200") > 0) //Look for 'ICY 200 OK'
|
||||
connectionSuccess = true;
|
||||
if (strstr(response, "401") > 0) //Look for '401 Unauthorized'
|
||||
{
|
||||
Serial.println("Hey - your credentials look bad! Check you caster username and password.");
|
||||
connectionSuccess = false;
|
||||
}
|
||||
}
|
||||
response[responseSpot] = '\0';
|
||||
|
||||
if (connectionSuccess == false)
|
||||
{
|
||||
Serial.print("Failed to connect to ");
|
||||
Serial.print(casterHost);
|
||||
Serial.print(": ");
|
||||
Serial.println(response);
|
||||
delay(5000); //Don't spam with lots of connection attempts
|
||||
}
|
||||
else
|
||||
{
|
||||
Serial.print("Connected to ");
|
||||
Serial.println(casterHost);
|
||||
lastReceivedRTCM_ms = millis(); //Reset timeout
|
||||
}
|
||||
} //End attempt to connect
|
||||
} //End connected == false
|
||||
|
||||
if (ntripClient.connected() == true)
|
||||
{
|
||||
uint8_t rtcmData[512 * 4]; //Most incoming data is around 500 bytes but may be larger
|
||||
rtcmCount = 0;
|
||||
|
||||
//Print any available RTCM data
|
||||
while (ntripClient.available())
|
||||
{
|
||||
//Serial.write(ntripClient.read()); //Pipe to serial port is fine but beware, it's a lot of binary data
|
||||
rtcmData[rtcmCount++] = ntripClient.read();
|
||||
if (rtcmCount == sizeof(rtcmData)) break;
|
||||
}
|
||||
|
||||
if (rtcmCount > 0)
|
||||
{
|
||||
lastReceivedRTCM_ms = millis();
|
||||
|
||||
//Push RTCM to GNSS module over I2C
|
||||
myGNSS.pushRawData(rtcmData, rtcmCount, false);
|
||||
Serial.print("RTCM pushed to ZED: ");
|
||||
Serial.println(rtcmCount);
|
||||
}
|
||||
}
|
||||
|
||||
//Close socket if we don't have new data for 10s
|
||||
if (millis() - lastReceivedRTCM_ms > maxTimeBeforeHangup_ms)
|
||||
{
|
||||
Serial.println("RTCM timeout. Disconnecting...");
|
||||
if (ntripClient.connected() == true)
|
||||
ntripClient.stop();
|
||||
return;
|
||||
}
|
||||
|
||||
delay(10);
|
||||
}
|
||||
|
||||
Serial.println("User pressed a key");
|
||||
Serial.println("Disconnecting...");
|
||||
ntripClient.stop();
|
||||
|
||||
while (Serial.available()) Serial.read(); //Empty buffer of any newline chars
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
//Your WiFi credentials
|
||||
const char ssid[] = "TRex";
|
||||
const char password[] = "hasBigTeeth";
|
||||
|
||||
//RTK2Go works well and is free
|
||||
const char casterHost[] = "rtk2go.com";
|
||||
const uint16_t casterPort = 2101;
|
||||
const char casterUser[] = "myEmail@test.com"; //User must provide their own email address to use RTK2Go
|
||||
const char casterUserPW[] = "";
|
||||
const char mountPoint[] = "bldr_SparkFun1"; //The mount point you want to get data from
|
||||
|
||||
//Emlid Caster also works well and is free
|
||||
//const char casterHost[] = "caster.emlid.com";
|
||||
//const uint16_t casterPort = 2101;
|
||||
//const char casterUser[] = "u99696"; //User name and pw must be obtained through their web portal
|
||||
//const char casterUserPW[] = "466zez";
|
||||
//const char mountPoint[] = "MP1979"; //The mount point you want to get data from
|
||||
Reference in New Issue
Block a user