Add Example17_NTRIPClient_With_GGA_Callback

This commit is contained in:
PaulZC
2022-01-13 20:40:13 +00:00
parent 3cede3bfff
commit 5558e73943
3 changed files with 525 additions and 1 deletions
+1 -1
View File
@@ -60,7 +60,7 @@ v2.1.0 of the library adds support for u-blox AssistNow<sup>TM</sup> Assisted GN
## Automatic support for correction services like RTK2go, Emlid Caster and Skylark (Swift Navigation) ## Automatic support for correction services like RTK2go, Emlid Caster and Skylark (Swift Navigation)
RTK NTRIP corrections services often require you to send them your location in NMEA GPGGA format. v2.2 of the library makes this simple by providing get functions and automatic callbacks RTK NTRIP corrections services often require you to send them your location in NMEA GPGGA format. v2.2 of the library makes this simple by providing get functions and automatic callbacks
for both GPGGA and GNGGA messages. You can now instruct your module to output GPGGA (e.g.) every 10 seconds and then push it to the correction server directly from the callback! No more polling, no more parsing! for both GPGGA and GNGGA messages. You can now instruct your module to output GPGGA (e.g.) every 10 seconds and then push it to the correction server directly from the callback. No more polling, no more parsing!
v2.2 also includes two new functions useful for correction services: v2.2 also includes two new functions useful for correction services:
@@ -0,0 +1,497 @@
/*
Use ESP32 WiFi to get RTCM data from Swift Navigation's Skylark caster as a Client, and transmit GGA using a callback
By: SparkFun Electronics / Nathan Seidle & Paul Clark
Date: January 13th, 2022
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 Skylark. But you can of course use RTK2Go or Emlid's Caster too. Change secrets.h. as required.
The rover's location will be broadcast to the caster every 10s via GGA setence - automatically using a callback.
This is a proof of concept to show how to connect to a caster via HTTP and show how the corrections control the accuracy.
It's a fun thing to disconnect from the caster and watch the accuracy degrade. Then connect again and watch it recover!
For more information about NTRIP Clients and the differences between Rev1 and Rev2 of the protocol
please see: https://www.use-snip.com/kb/knowledge-base/ntrip-rev1-versus-rev2-formats/
"In broad protocol terms, the NTRIP client must first connect (get an HTTP “OK” reply) and only then
should it send the sentence. NTRIP protocol revision 2 (which does not have very broad industry
acceptance at this time) does allow sending the sentence in the original header."
https://www.use-snip.com/kb/knowledge-base/subtle-issues-with-using-ntrip-client-nmea-183-strings/
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
unsigned long lastReceivedRTCM_ms = 0; //5 RTCM messages take approximately ~300ms to arrive at 115200bps
const unsigned long maxTimeBeforeHangup_ms = 10000UL; //If we fail to get a complete RTCM frame after 10s, then disconnect from caster
bool transmitLocation = true; //By default we will transmit the unit's location via GGA sentence.
WiFiClient ntripClient; // The WiFi connection to the NTRIP server. This is global so pushGGA can see if we are connected.
//=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
// Callback: pushGPGGA will be called when new GPGGA NMEA data arrives
// See u-blox_structs.h for the full definition of NMEA_GGA_data_t
// _____ You can use any name you like for the callback. Use the same name when you call setNMEAGPGGAcallback
// / _____ This _must_ be NMEA_GGA_data_t
// | / _____ You can use any name you like for the struct
// | | /
// | | |
void pushGPGGA(NMEA_GGA_data_t nmeaData)
{
//Provide the caster with our current position as needed
if ((ntripClient.connected() == true) && (transmitLocation == true))
{
Serial.print(F("Pushing GGA to server: "));
Serial.print((const char *)nmeaData.nmea); // .nmea is printable (NULL-terminated) and already has \r\n on the end
//Push our current GGA sentence to caster
ntripClient.print((const char *)nmeaData.nmea);
}
}
//=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
// Callback: printPVTdata will be called when new NAV PVT data arrives
// See u-blox_structs.h for the full definition of UBX_NAV_PVT_data_t
// _____ You can use any name you like for the callback. Use the same name when you call setAutoPVTcallback
// / _____ This _must_ be UBX_NAV_PVT_data_t
// | / _____ You can use any name you like for the struct
// | | /
// | | |
void printPVTdata(UBX_NAV_PVT_data_t ubxDataStruct)
{
long latitude = ubxDataStruct.lat; // Print the latitude
Serial.print(F("Lat: "));
Serial.print(latitude / 10000000L);
Serial.print(F("."));
Serial.print(abs(latitude % 10000000L));
long longitude = ubxDataStruct.lon; // Print the longitude
Serial.print(F(" Long: "));
Serial.print(longitude / 10000000L);
Serial.print(F("."));
Serial.print(abs(longitude % 10000000L));
long altitude = ubxDataStruct.hMSL; // Print the height above mean sea level
Serial.print(F(" Height: "));
Serial.print(altitude);
Serial.print(F(" (mm)"));
uint8_t fixType = ubxDataStruct.fixType; // Print the fix type
Serial.print(F(" Fix: "));
Serial.print(fixType);
if (fixType == 0)
Serial.print(F(" (None)"));
else if (fixType == 1)
Serial.print(F(" (Dead Reckoning)"));
else if (fixType == 2)
Serial.print(F(" (2D)"));
else if (fixType == 3)
Serial.print(F(" (3D)"));
else if (fixType == 3)
Serial.print(F(" (GNSS + Dead Reckoning)"));
else if (fixType == 5)
Serial.print(F(" (Time Only)"));
else
Serial.print(F(" (UNKNOWN)"));
uint8_t carrSoln = ubxDataStruct.flags.bits.carrSoln; // Print the carrier solution
Serial.print(F(" Carrier Solution: "));
Serial.print(carrSoln);
if (carrSoln == 0)
Serial.print(F(" (None)"));
else if (carrSoln == 1)
Serial.print(F(" (Floating)"));
else if (carrSoln == 2)
Serial.print(F(" (Fixed)"));
else
Serial.print(F(" (UNKNOWN)"));
uint32_t hAcc = ubxDataStruct.hAcc; // Print the horizontal accuracy estimate
Serial.print(F(" Horizontal Accuracy Estimate: "));
Serial.print(hAcc);
Serial.print(F(" (mm)"));
Serial.println();
}
//=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
void setup()
{
Serial.begin(115200);
Serial.println(F("NTRIP testing"));
Wire.begin(); //Start I2C
while (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."));
delay(2000);
//while (1);
}
Serial.println(F("u-blox module connected"));
myGNSS.setI2COutput(COM_TYPE_UBX | COM_TYPE_NMEA); //Set the I2C port to output both NMEA and UBX messages
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.setDGNSSConfiguration(SFE_UBLOX_DGNSS_MODE_FIXED); // Set the differential mode - ambiguities are fixed whenever possible
myGNSS.setNavigationFrequency(1); //Set output in Hz.
// Set the Main Talker ID to "GP". The NMEA GGA messages will be GPGGA instead of GNGGA
myGNSS.setMainTalkerID(SFE_UBLOX_MAIN_TALKER_ID_GP);
myGNSS.setNMEAGPGGAcallback(&pushGPGGA); // Set up the callback for GPGGA
myGNSS.enableNMEAMessage(UBX_NMEA_GGA, COM_PORT_I2C, 10); // Tell the module to output GGA every 10 seconds
myGNSS.setAutoPVTcallback(&printPVTdata); // Enable automatic NAV PVT messages with callback to printPVTdata so we can watch the carrier solution go to fixed
//myGNSS.saveConfiguration(VAL_CFG_SUBSEC_IOPORT | VAL_CFG_SUBSEC_MSGCONF); //Optional: Save the ioPort and message settings to NVM
//=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
bool keepTrying = true;
while (keepTrying)
{
Serial.print(F("Connecting to local WiFi"));
unsigned long startTime = millis();
WiFi.begin(ssid, password);
while ((WiFi.status() != WL_CONNECTED) && (millis() < (startTime + 10000))) // Timeout after 10 seconds
{
delay(500);
Serial.print(F("."));
}
Serial.println();
if (WiFi.status() == WL_CONNECTED)
keepTrying = false; // Connected!
else
{
WiFi.disconnect(true);
WiFi.mode(WIFI_OFF);
}
}
Serial.print(F("WiFi connected with IP: "));
Serial.println(WiFi.localIP());
//=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
while (Serial.available()) // Empty the serial buffer
Serial.read();
}
//=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
void loop()
{
myGNSS.checkUblox(); // Check for the arrival of new GNSS data and process it.
myGNSS.checkCallbacks(); // Check if any GNSS callbacks are waiting to be processed.
enum states // Use a 'state machine' to open and close the connection
{
open_connection,
push_data_and_wait_for_keypress,
close_connection,
waiting_for_keypress
};
static states state = open_connection;
//=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
switch (state)
{
case open_connection:
Serial.println(F("Connecting to the NTRIP caster..."));
if (beginClient()) // Try to open the connection to the caster
{
Serial.println(F("Connected to the NTRIP caster! Press any key to disconnect..."));
state = push_data_and_wait_for_keypress; // Move on
}
else
{
Serial.print(F("Could not connect to the caster. Trying again in 5 seconds."));
for (int i = 0; i < 5; i++)
{
delay(1000);
Serial.print(F("."));
}
Serial.println();
}
break;
//=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
case push_data_and_wait_for_keypress:
// If the connection has dropped or timed out, or if the user has pressed a key
if ((processConnection() == false) || (keyPressed()))
{
state = close_connection; // Move on
}
break;
//=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
case close_connection:
Serial.println(F("Closing the connection to the NTRIP caster..."));
closeConnection();
Serial.println(F("Press any key to reconnect..."));
state = waiting_for_keypress; // Move on
break;
//=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
case waiting_for_keypress:
// If the connection has dropped or timed out, or if the user has pressed a key
if (keyPressed())
state = open_connection; // Move on
break;
}
}
//=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
//Connect to NTRIP Caster. Return true is connection is successful.
bool beginClient()
{
Serial.print(F("Opening socket to "));
Serial.println(casterHost);
if (ntripClient.connect(casterHost, casterPort) == false) //Attempt connection
{
Serial.println(F("Connection to caster failed"));
return (false);
}
else
{
Serial.print(F("Connected to "));
Serial.print(casterHost);
Serial.print(F(" : "));
Serial.println(casterPort);
Serial.print(F("Requesting NTRIP Data from mount point "));
Serial.println(mountPoint);
// Set up the server request (GET)
const int SERVER_BUFFER_SIZE = 512;
char serverRequest[SERVER_BUFFER_SIZE];
snprintf(serverRequest,
SERVER_BUFFER_SIZE,
"GET /%s HTTP/1.0\r\nUser-Agent: NTRIP SparkFun u-blox Client v1.0\r\n",
mountPoint);
// Set up the credentials
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(F("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
#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
snprintf(credentials, sizeof(credentials), "Authorization: Basic %s\r\n", encodedCredentials);
}
// Add the encoded credentials to the server request
strncat(serverRequest, credentials, SERVER_BUFFER_SIZE);
strncat(serverRequest, "\r\n", SERVER_BUFFER_SIZE);
Serial.print(F("serverRequest size: "));
Serial.print(strlen(serverRequest));
Serial.print(F(" of "));
Serial.print(sizeof(serverRequest));
Serial.println(F(" bytes available"));
// Send the server request
Serial.println(F("Sending server request: "));
Serial.println(serverRequest);
ntripClient.write(serverRequest, strlen(serverRequest));
//Wait up to 5 seconds for response
unsigned long startTime = millis();
while (ntripClient.available() == 0)
{
if (millis() > (startTime + 5000))
{
Serial.println(F("Caster timed out!"));
ntripClient.stop();
return (false);
}
delay(10);
}
//Check reply
int connectionResult = 0;
char response[512];
size_t responseSpot = 0;
while (ntripClient.available()) // Read bytes from the caster and store them
{
if (responseSpot == sizeof(response) - 1) // Exit the loop if we get too much data
break;
response[responseSpot++] = ntripClient.read();
if (connectionResult == 0) // Only print success/fail once
{
if (strstr(response, "200") != NULL) //Look for '200 OK'
{
connectionResult = 200;
}
if (strstr(response, "401") != NULL) //Look for '401 Unauthorized'
{
Serial.println(F("Hey - your credentials look bad! Check your caster username and password."));
connectionResult = 401;
}
}
}
response[responseSpot] = '\0'; // NULL-terminate the response
//Serial.print(F("Caster responded with: ")); Serial.println(response); // Uncomment this line to see the full response
if (connectionResult != 200)
{
Serial.print(F("Failed to connect to "));
Serial.println(casterHost);
return (false);
}
else
{
Serial.print(F("Connected to: "));
Serial.println(casterHost);
lastReceivedRTCM_ms = millis(); //Reset timeout
}
} //End attempt to connect
return (true);
} // /beginClient
//=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
//Check for the arrival of any correction data. Push it to the GNSS.
//Return false if: the connection has dropped, or if we receive no data for maxTimeBeforeHangup_ms
bool processConnection()
{
if (ntripClient.connected() == true) // Check that the connection is still open
{
uint8_t rtcmData[512 * 4]; //Most incoming data is around 500 bytes but may be larger
size_t rtcmCount = 0;
//Collect 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);
Serial.print(F("Pushed "));
Serial.print(rtcmCount);
Serial.println(F(" RTCM bytes to ZED"));
}
}
else
{
Serial.println(F("Connection dropped!"));
return (false); // Connection has dropped - return false
}
//Timeout if we don't have new data for maxTimeBeforeHangup_ms
if ((millis() - lastReceivedRTCM_ms) > maxTimeBeforeHangup_ms)
{
Serial.println(F("RTCM timeout!"));
return (false); // Connection has timed out - return false
}
return (true);
} // /processConnection
//=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
void closeConnection()
{
if (ntripClient.connected() == true)
{
ntripClient.stop();
}
Serial.println(F("Disconnected!"));
}
//=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
//Return true if a key has been pressed
bool keyPressed()
{
if (Serial.available()) // Check for a new key press
{
delay(100); // Wait for any more keystrokes to arrive
while (Serial.available()) // Empty the serial buffer
Serial.read();
return (true);
}
return (false);
}
@@ -0,0 +1,27 @@
//Your WiFi credentials
const char ssid[] = "yourSSID";
const char password[] = "yourPassword";
//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
// Skylark (Swift Navigation) is awesome - but requires a subscription:
// https://www.swiftnav.com/skylark
// https://account.swiftnav.com/sign-up
// Use the promo-code ONEMONTHFREE for a free one month access to Skylark on one device
const char casterHost[] = "na.skylark.swiftnav.com"; // na = North Americs L1+L2; eu = Europe L1+L2
const uint16_t casterPort = 2101;
const char casterUser[] = "NTRIPusername+accountSubdomain"; // This is generated when you add a device to your Skylark account
const char casterUserPW[] = "devicePassword";
const char mountPoint[] = "CRS"; // The mount point you want to get data from. Select CRS (Cloud Reference Station) for the ZED-F9x