diff --git a/examples/ZED-F9P/Example15_NTRIPClient/Example15_NTRIPClient.ino b/examples/ZED-F9P/Example15_NTRIPClient/Example15_NTRIPClient.ino new file mode 100644 index 0000000..7e9fb72 --- /dev/null +++ b/examples/ZED-F9P/Example15_NTRIPClient/Example15_NTRIPClient.ino @@ -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 +#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 +} diff --git a/examples/ZED-F9P/Example15_NTRIPClient/secrets.h b/examples/ZED-F9P/Example15_NTRIPClient/secrets.h new file mode 100644 index 0000000..55f3b2a --- /dev/null +++ b/examples/ZED-F9P/Example15_NTRIPClient/secrets.h @@ -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