/* 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 #include "secrets.h" #include //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 //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 }