diff --git a/examples/ZED-F9P/Example14_NTRIPServer/Example14_NTRIPServer.ino b/examples/ZED-F9P/Example14_NTRIPServer/Example14_NTRIPServer.ino index ca01530..aebdd31 100644 --- a/examples/ZED-F9P/Example14_NTRIPServer/Example14_NTRIPServer.ino +++ b/examples/ZED-F9P/Example14_NTRIPServer/Example14_NTRIPServer.ino @@ -1,7 +1,5 @@ /* - Note: compiles OK with v2.0 but is currently untested - - Use ESP32 WiFi to push RTCM data to RTK2Go (caster) as a Server + Use ESP32 WiFi to push RTCM data to RTK2Go (Caster) as a Server By: SparkFun Electronics / Nathan Seidle Date: December 14th, 2020 License: MIT. See license file for more information but you can @@ -33,26 +31,21 @@ #include #include "secrets.h" -WiFiClient client; +WiFiClient ntripCaster; -#include //Needed for I2C to GNSS +#include #include //http://librarymanager/All#SparkFun_u-blox_GNSS SFE_UBLOX_GNSS myGNSS; -//Basic Connection settings to RTK2Go NTRIP Caster - See secrets for mount specific credentials +//Global Variables //=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= -const uint16_t casterPort = 2101; -const char * casterHost = "rtk2go.com"; -const char * ntrip_server_name = "SparkFun_RTK_Surveyor"; - -long lastSentRTCM_ms = 0; //Time of last data pushed to socket +long lastSentRTCM_ms = 0; //Time of last data pushed to socket int maxTimeBeforeHangup_ms = 10000; //If we fail to get a complete RTCM frame after 10s, then disconnect from caster uint32_t serverBytesSent = 0; //Just a running total +long lastReport_ms = 0; //Time of last report of bytes sent //=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= -long lastReport_ms = 0; //Time of last report of bytes sent - void setup() { Serial.begin(115200); // You may need to increase this for high navigation rates! @@ -73,7 +66,8 @@ void setup() Serial.print("Connecting to local WiFi"); WiFi.begin(ssid, password); - while (WiFi.status() != WL_CONNECTED) { + while (WiFi.status() != WL_CONNECTED) + { delay(500); Serial.print("."); } @@ -98,7 +92,8 @@ void setup() if (response == false) { Serial.println(F("Failed to disable NMEA. Freezing...")); - while (1); + while (1) + ; } else Serial.println(F("NMEA disabled")); @@ -114,7 +109,8 @@ void setup() if (response == false) { Serial.println(F("Failed to enable RTCM. Freezing...")); - while (1); + while (1) + ; } else Serial.println(F("RTCM sentences enabled")); @@ -129,63 +125,72 @@ void setup() if (response == false) { Serial.println(F("Failed to enter static position. Freezing...")); - while (1); + while (1) + ; } else Serial.println(F("Static position set")); - //You could instead do a survey-in but it takes much longer to start generating RTCM data. See Example4_BaseWithLCD + //Alternatively to setting a static position, you could do a survey-in + //but it takes much longer to start generating RTCM data. See Example4_BaseWithLCD //myGNSS.enableSurveyMode(60, 5.000); //Enable Survey in, 60 seconds, 5.0m - if (myGNSS.saveConfiguration() == false) //Save the current settings to flash and BBR - Serial.println(F("Module failed to save.")); + //If you were setting up a full GNSS station, you would want to save these settings. + //Because setting an incorrect static position will disable the ability to get a lock, we will skip saving during this example + //if (myGNSS.saveConfiguration() == false) //Save the current settings to flash and BBR + // Serial.println(F("Module failed to save")); Serial.println(F("Module configuration complete")); } void loop() { - if (Serial.available()) beginServing(); + if (Serial.available()) + beginServing(); - Serial.println(F("Press any key to start serving.")); + Serial.println(F("Press any key to start serving")); delay(1000); } void beginServing() { - Serial.println("Xmit to RTK2Go. Press any key to stop"); + Serial.println("Begin transmitting to caster. Press any key to stop"); delay(10); //Wait for any serial to arrive - while (Serial.available()) Serial.read(); //Flush + while (Serial.available()) + Serial.read(); //Flush while (Serial.available() == 0) { //Connect if we are not already - if (client.connected() == false) + if (ntripCaster.connected() == false) { Serial.printf("Opening socket to %s\n", casterHost); - if (client.connect(casterHost, casterPort) == true) //Attempt connection + if (ntripCaster.connect(casterHost, casterPort) == true) //Attempt connection { Serial.printf("Connected to %s:%d\n", casterHost, casterPort); - const int SERVER_BUFFER_SIZE = 512; - char serverBuffer[SERVER_BUFFER_SIZE]; + const int SERVER_BUFFER_SIZE = 512; + char serverRequest[SERVER_BUFFER_SIZE]; - snprintf(serverBuffer, SERVER_BUFFER_SIZE, "SOURCE %s /%s\r\nSource-Agent: NTRIP %s/%s\r\n\r\n", - mntpnt_pw, mntpnt, ntrip_server_name, "App Version 1.0"); + snprintf(serverRequest, + SERVER_BUFFER_SIZE, + "SOURCE %s /%s\r\nSource-Agent: NTRIP SparkFun u-blox Server v1.0\r\n\r\n", + mountPointPW, mountPoint); - Serial.printf("Sending credentials:\n%s\n", serverBuffer); - client.write(serverBuffer, strlen(serverBuffer)); + Serial.println(F("Sending server request:")); + Serial.println(serverRequest); + ntripCaster.write(serverRequest, strlen(serverRequest)); //Wait for response unsigned long timeout = millis(); - while (client.available() == 0) + while (ntripCaster.available() == 0) { if (millis() - timeout > 5000) { - Serial.println(">>> Client Timeout !"); - client.stop(); + Serial.println("Caster timed out!"); + ntripCaster.stop(); return; } delay(10); @@ -195,30 +200,34 @@ void beginServing() bool connectionSuccess = false; char response[512]; int responseSpot = 0; - while (client.available()) + while (ntripCaster.available()) { - response[responseSpot++] = client.read(); + response[responseSpot++] = ntripCaster.read(); if (strstr(response, "200") > 0) //Look for 'ICY 200 OK' connectionSuccess = true; - if (responseSpot == 512 - 1) break; + if (responseSpot == 512 - 1) + break; } response[responseSpot] = '\0'; if (connectionSuccess == false) { - Serial.printf("Failed to connect to RTK2Go: %s", response); + Serial.printf("Failed to connect to Caster: %s", response); + return; } } //End attempt to connect else { Serial.println("Connection to host failed"); + return; } } //End connected == false - if (client.connected() == true) + if (ntripCaster.connected() == true) { delay(10); - while (Serial.available()) Serial.read(); //Flush any endlines or carriage returns + while (Serial.available()) + Serial.read(); //Flush any endlines or carriage returns lastReport_ms = millis(); lastSentRTCM_ms = millis(); @@ -226,7 +235,8 @@ void beginServing() //This is the main sending loop. We scan for new ublox data but processRTCM() is where the data actually gets sent out. while (1) { - if (Serial.available()) break; + if (Serial.available()) + break; myGNSS.checkUblox(); //See if new data is available. Process bytes as they come in. @@ -236,7 +246,7 @@ void beginServing() if (millis() - lastSentRTCM_ms > maxTimeBeforeHangup_ms) { Serial.println("RTCM timeout. Disconnecting..."); - client.stop(); + ntripCaster.stop(); return; } @@ -256,10 +266,11 @@ void beginServing() Serial.println("User pressed a key"); Serial.println("Disconnecting..."); - client.stop(); + ntripCaster.stop(); delay(10); - while (Serial.available()) Serial.read(); //Flush any endlines or carriage returns + while (Serial.available()) + Serial.read(); //Flush any endlines or carriage returns } //This function gets called from the SparkFun u-blox Arduino Library. @@ -267,10 +278,10 @@ void beginServing() //Useful for passing the RTCM correction data to a radio, Ntrip broadcaster, etc. void SFE_UBLOX_GNSS::processRTCM(uint8_t incoming) { - if (client.connected() == true) + if (ntripCaster.connected() == true) { - client.write(incoming); //Send this byte to socket + ntripCaster.write(incoming); //Send this byte to socket serverBytesSent++; lastSentRTCM_ms = millis(); } -} +} \ No newline at end of file diff --git a/examples/ZED-F9P/Example14_NTRIPServer/secrets.h b/examples/ZED-F9P/Example14_NTRIPServer/secrets.h index 5dcd50e..8cd0920 100644 --- a/examples/ZED-F9P/Example14_NTRIPServer/secrets.h +++ b/examples/ZED-F9P/Example14_NTRIPServer/secrets.h @@ -1,7 +1,15 @@ //Your WiFi credentials -const char* ssid = "TRex"; -const char* password = "hasBigTeeth"; +const char *ssid = "TRex"; +const char *password = "hasBigTeeth"; -//Your RTK2GO mount point credentials -const char* mntpnt_pw = "WR5wRo4H"; -const char* mntpnt = "bldr_dwntwn2"; +//RTK2Go works well and is free +const char casterHost[] = "rtk2go.com"; +const uint16_t casterPort = 2101; +const char mountPoint[] = "bldr_dwntwn2"; //The mount point you want to push data to +const char mountPointPW[] = "WR5wRo4H"; + +//Emlid Caster also works well and is free +//const char casterHost[] = "caster.emlid.com"; +//const uint16_t casterPort = 2101; +//const char mountPoint[] = "MP1979d"; //The mount point you want to push data to +//const char mountPointPW[] = "296ynq"; diff --git a/examples/ZED-F9P/Example15_NTRIPClient/Example15_NTRIPClient.ino b/examples/ZED-F9P/Example15_NTRIPClient/Example15_NTRIPClient.ino index 7e9fb72..f739b9e 100644 --- a/examples/ZED-F9P/Example15_NTRIPClient/Example15_NTRIPClient.ino +++ b/examples/ZED-F9P/Example15_NTRIPClient/Example15_NTRIPClient.ino @@ -16,6 +16,9 @@ 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. + 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/ + Feel like supporting open source hardware? Buy a board from SparkFun! ZED-F9P RTK2: https://www.sparkfun.com/products/16481 @@ -50,7 +53,7 @@ int maxTimeBeforeHangup_ms = 10000; //If we fail to get a complete RTCM frame af void setup() { Serial.begin(115200); - Serial.println("NTRIP testing"); + Serial.println(F("NTRIP testing")); Wire.begin(); //Start I2C @@ -66,15 +69,15 @@ void setup() myGNSS.setNavigationFrequency(1); //Set output in Hz. - Serial.print("Connecting to local WiFi"); + Serial.print(F("Connecting to local WiFi")); WiFi.begin(ssid, password); while (WiFi.status() != WL_CONNECTED) { delay(500); - Serial.print("."); + Serial.print(F(".")); } Serial.println(); - Serial.print("WiFi connected with IP: "); + Serial.print(F("WiFi connected with IP: ")); Serial.println(WiFi.localIP()); while (Serial.available()) Serial.read(); @@ -82,7 +85,11 @@ void setup() void loop() { - if (Serial.available()) beginClient(); + if (Serial.available()) + { + beginClient(); + while (Serial.available()) Serial.read(); //Empty buffer of any newline chars + } Serial.println(F("Press any key to start NTRIP Client.")); @@ -95,36 +102,37 @@ void beginClient() WiFiClient ntripClient; long rtcmCount = 0; - Serial.println("Subscribing to Caster. Press key to stop"); + Serial.println(F("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 + //Connect if we are not already. Limit to 5s between attempts. if (ntripClient.connected() == false) { - Serial.print("Opening socket to"); + Serial.print(F("Opening socket to ")); Serial.println(casterHost); if (ntripClient.connect(casterHost, casterPort) == false) //Attempt connection { - Serial.println("Connection to caster failed"); + Serial.println(F("Connection to caster failed")); + return; } else { - Serial.print("Connected to "); + Serial.print(F("Connected to ")); Serial.print(casterHost); - Serial.print(": "); + Serial.print(F(": ")); Serial.println(casterPort); - Serial.print("Requesting NTRIP Data from mount point "); + Serial.print(F("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", + snprintf(serverRequest, SERVER_BUFFER_SIZE, "GET /%s HTTP/1.0\r\nUser-Agent: NTRIP SparkFun u-blox Client v1.0\r\n", mountPoint); char credentials[512]; @@ -138,7 +146,7 @@ void beginClient() 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.print(F("Sending credentials: ")); Serial.println(userCredentials); #if defined(ARDUINO_ARCH_ESP32) @@ -158,13 +166,13 @@ void beginClient() strncat(serverRequest, credentials, SERVER_BUFFER_SIZE); strncat(serverRequest, "\r\n", SERVER_BUFFER_SIZE); - Serial.print("serverRequest size: "); + Serial.print(F("serverRequest size: ")); Serial.print(strlen(serverRequest)); - Serial.print(" of "); + Serial.print(F(" of ")); Serial.print(sizeof(serverRequest)); - Serial.println(" bytes available"); + Serial.println(F(" bytes available")); - Serial.println("Sending server request:"); + Serial.println(F("Sending server request:")); Serial.println(serverRequest); ntripClient.write(serverRequest, strlen(serverRequest)); @@ -174,7 +182,7 @@ void beginClient() { if (millis() - timeout > 5000) { - Serial.println("Mountpoint timed out!"); + Serial.println(F("Caster timed out!")); ntripClient.stop(); return; } @@ -194,23 +202,26 @@ void beginClient() connectionSuccess = true; if (strstr(response, "401") > 0) //Look for '401 Unauthorized' { - Serial.println("Hey - your credentials look bad! Check you caster username and password."); + Serial.println(F("Hey - your credentials look bad! Check you caster username and password.")); connectionSuccess = false; } } response[responseSpot] = '\0'; + Serial.print(F("Caster responded with: ")); + Serial.println(response); + if (connectionSuccess == false) { - Serial.print("Failed to connect to "); + Serial.print(F("Failed to connect to ")); Serial.print(casterHost); - Serial.print(": "); + Serial.print(F(": ")); Serial.println(response); - delay(5000); //Don't spam with lots of connection attempts + return; } else { - Serial.print("Connected to "); + Serial.print(F("Connected to ")); Serial.println(casterHost); lastReceivedRTCM_ms = millis(); //Reset timeout } @@ -236,7 +247,7 @@ void beginClient() //Push RTCM to GNSS module over I2C myGNSS.pushRawData(rtcmData, rtcmCount, false); - Serial.print("RTCM pushed to ZED: "); + Serial.print(F("RTCM pushed to ZED: ")); Serial.println(rtcmCount); } } @@ -244,7 +255,7 @@ void beginClient() //Close socket if we don't have new data for 10s if (millis() - lastReceivedRTCM_ms > maxTimeBeforeHangup_ms) { - Serial.println("RTCM timeout. Disconnecting..."); + Serial.println(F("RTCM timeout. Disconnecting...")); if (ntripClient.connected() == true) ntripClient.stop(); return; @@ -253,9 +264,7 @@ void beginClient() delay(10); } - Serial.println("User pressed a key"); - Serial.println("Disconnecting..."); + Serial.println(F("User pressed a key")); + Serial.println(F("Disconnecting...")); ntripClient.stop(); - - while (Serial.available()) Serial.read(); //Empty buffer of any newline chars -} +} \ No newline at end of file diff --git a/examples/ZED-F9P/Example16_NTRIPClient_WithGGA/Example16_NTRIPClient_WithGGA.ino b/examples/ZED-F9P/Example16_NTRIPClient_WithGGA/Example16_NTRIPClient_WithGGA.ino new file mode 100644 index 0000000..b2506eb --- /dev/null +++ b/examples/ZED-F9P/Example16_NTRIPClient_WithGGA/Example16_NTRIPClient_WithGGA.ino @@ -0,0 +1,403 @@ +/* + Use ESP32 WiFi to get RTCM data from RTK2Go (caster) as a Client, and transmit GGA (needed for some Casters) + 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. + + The rover's location will be broadcast to the Caster every 10s via GGA setence. + + 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. + + 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 +#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 + +bool transmitLocation = true; //By default we will transmit the units location via GGA sentence. +int timeBetweenGGAUpdate_ms = 10000; //GGA is required for Rev2 NTRIP casters. Don't transmit but once every 10 seconds +long lastTransmittedGGA_ms = 0; + +//Used for GGA sentence parsing from incoming NMEA +bool ggaSentenceStarted = false; +bool ggaSentenceComplete = false; +bool ggaTransmitComplete = false; //Goes true once we transmit GGA to the caster + +char ggaSentence[128] = {0}; +byte ggaSentenceSpot = 0; +int ggaSentenceEndSpot = 0; +//=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + +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.enableNMEAMessage(UBX_NMEA_GGA, COM_PORT_I2C); //Verify the GGA sentence is enabled + + myGNSS.setNavigationFrequency(1); //Set output in Hz. + + Serial.print(F("Connecting to local WiFi")); + WiFi.begin(ssid, password); + while (WiFi.status() != WL_CONNECTED) + { + delay(500); + Serial.print(F(".")); + } + Serial.println(); + + Serial.print(F("WiFi connected with IP: ")); + Serial.println(WiFi.localIP()); + + while (Serial.available()) + Serial.read(); +} + +void loop() +{ + if (Serial.available()) + { + beginClient(); + while (Serial.available()) + Serial.read(); //Empty buffer of any newline chars + } + + 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(F("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) + { + myGNSS.checkUblox(); + + //Connect if we are not already. Limit to 5s between attempts. + if (ntripClient.connected() == false) + { + 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; + } + 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); + + 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); + + 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); + } + 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")); + + Serial.println(F("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(F("Caster 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 '200 OK' + connectionSuccess = true; + if (strstr(response, "401") > 0) //Look for '401 Unauthorized' + { + Serial.println(F("Hey - your credentials look bad! Check you caster username and password.")); + connectionSuccess = false; + } + } + response[responseSpot] = '\0'; + + Serial.print(F("Caster responded with: ")); + Serial.println(response); + + if (connectionSuccess == false) + { + Serial.print(F("Failed to connect to ")); + Serial.println(casterHost); + return; + } + else + { + Serial.print(F("Connected to ")); + Serial.println(casterHost); + lastReceivedRTCM_ms = millis(); //Reset timeout + ggaTransmitComplete = true; //Reset to start polling for new GGA data + } + } //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(F("RTCM pushed to ZED: ")); + Serial.println(rtcmCount); + } + } + + //Provide the caster with our current position as needed + if (ntripClient.connected() == true && transmitLocation == true && (millis() - lastTransmittedGGA_ms) > timeBetweenGGAUpdate_ms && ggaSentenceComplete == true && ggaTransmitComplete == false) + { + Serial.print(F("Pushing GGA to server: ")); + Serial.println(ggaSentence); + + lastTransmittedGGA_ms = millis(); + + //Push our current GGA sentence to caster + ntripClient.print(ggaSentence); + ntripClient.print("\r\n"); + + ggaTransmitComplete = true; + + //Wait for response + unsigned long timeout = millis(); + while (ntripClient.available() == 0) + { + if (millis() - timeout > 5000) + { + Serial.println(F("Caster 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 '200 OK' + connectionSuccess = true; + if (strstr(response, "401") > 0) //Look for '401 Unauthorized' + { + Serial.println(F("Hey - your credentials look bad! Check you caster username and password.")); + connectionSuccess = false; + } + } + response[responseSpot] = '\0'; + + Serial.print(F("Caster responded with: ")); + Serial.println(response); + } + + //Close socket if we don't have new data for 10s + if (millis() - lastReceivedRTCM_ms > maxTimeBeforeHangup_ms) + { + Serial.println(F("RTCM timeout. Disconnecting...")); + if (ntripClient.connected() == true) + ntripClient.stop(); + return; + } + + delay(10); + } + + Serial.println(F("User pressed a key")); + Serial.println(F("Disconnecting...")); + ntripClient.stop(); +} + +//This function gets called from the SparkFun u-blox Arduino Library +//As each NMEA character comes in you can specify what to do with it +//We will look for and copy the GGA sentence +void SFE_UBLOX_GNSS::processNMEA(char incoming) +{ + //Take the incoming char from the u-blox I2C port and check to see if we should record it or not + if (incoming == '$' && ggaTransmitComplete == true) + { + ggaSentenceStarted = true; + ggaSentenceSpot = 0; + ggaSentenceEndSpot = sizeof(ggaSentence); + ggaSentenceComplete = false; + } + + if (ggaSentenceStarted == true) + { + ggaSentence[ggaSentenceSpot++] = incoming; + + //Make sure we don't go out of bounds + if (ggaSentenceSpot == sizeof(ggaSentence)) + { + //Start over + ggaSentenceStarted = false; + } + //Verify this is the GGA setence + else if (ggaSentenceSpot == 5 && incoming != 'G') + { + //Ignore this sentence, start over + ggaSentenceStarted = false; + } + else if (incoming == '*') + { + //We're near the end. Keep listening for two more bytes to complete the CRC + ggaSentenceEndSpot = ggaSentenceSpot + 2; + } + else if (ggaSentenceSpot == ggaSentenceEndSpot) + { + ggaSentence[ggaSentenceSpot] = '\0'; //Terminate this string + ggaSentenceComplete = true; + ggaTransmitComplete = false; //We are ready for transmission + + //Serial.print("GGA Parsed - "); + //Serial.println(ggaSentence); + + //Start over + ggaSentenceStarted = false; + } + } +} \ No newline at end of file diff --git a/examples/ZED-F9P/Example16_NTRIPClient_WithGGA/secrets.h b/examples/ZED-F9P/Example16_NTRIPClient_WithGGA/secrets.h new file mode 100644 index 0000000..3a6becf --- /dev/null +++ b/examples/ZED-F9P/Example16_NTRIPClient_WithGGA/secrets.h @@ -0,0 +1,17 @@ +//Your WiFi credentials +const char ssid[] = "TRex"; +const char password[] = "parachutes"; + +//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 diff --git a/library.properties b/library.properties index b06dd6b..ce120ad 100644 --- a/library.properties +++ b/library.properties @@ -1,5 +1,5 @@ name=SparkFun u-blox GNSS Arduino Library -version=2.1.3 +version=2.1.4 author=SparkFun Electronics maintainer=SparkFun Electronics sentence=Library for I2C and Serial Communication with u-blox GNSS modules

diff --git a/src/SparkFun_u-blox_GNSS_Arduino_Library.cpp b/src/SparkFun_u-blox_GNSS_Arduino_Library.cpp index 34419a0..0cba6fb 100644 --- a/src/SparkFun_u-blox_GNSS_Arduino_Library.cpp +++ b/src/SparkFun_u-blox_GNSS_Arduino_Library.cpp @@ -442,6 +442,7 @@ bool SFE_UBLOX_GNSS::begin(TwoWire &wirePort, uint8_t deviceAddress, uint16_t ma { commType = COMM_TYPE_I2C; _i2cPort = &wirePort; //Grab which port the user wants us to use + _signsOfLife = false; //Clear the _signsOfLife flag. It will be set true if valid traffic is seen. //We expect caller to begin their I2C port, with the speed of their choice external to the library //But if they forget, we start the hardware here. @@ -485,7 +486,7 @@ bool SFE_UBLOX_GNSS::begin(TwoWire &wirePort, uint8_t deviceAddress, uint16_t ma connected = isConnected(maxWait); } - if ((!connected ) && assumeSuccess) // Advanced users can assume success if required. Useful if the port is outputting messages at high navigation rate. + if ((!connected ) && assumeSuccess && _signsOfLife) // Advanced users can assume success if required. Useful if the port is outputting messages at high navigation rate. { #ifndef SFE_UBLOX_REDUCED_PROG_MEM if ((_printDebug == true) || (_printLimitedDebug == true)) // This is important. Print this if doing limited debugging @@ -504,6 +505,7 @@ bool SFE_UBLOX_GNSS::begin(Stream &serialPort, uint16_t maxWait, bool assumeSucc { commType = COMM_TYPE_SERIAL; _serialPort = &serialPort; //Grab which port the user wants us to use + _signsOfLife = false; //Clear the _signsOfLife flag. It will be set true if valid traffic is seen. //New in v2.0: allocate memory for the packetCfg payload here - if required. (The user may have called setPacketCfgPayloadSize already) if (packetCfgPayloadSize == 0) @@ -512,6 +514,30 @@ bool SFE_UBLOX_GNSS::begin(Stream &serialPort, uint16_t maxWait, bool assumeSucc //New in v2.0: allocate memory for the file buffer - if required. (The user should have called setFileBufferSize already) createFileBuffer(); + //Get rid of any stale serial data already in the processor's RX buffer + while (_serialPort->available()) + _serialPort->read(); + + //If assumeSuccess is true, the user must really want begin to succeed. So, let's empty the module's serial transmit buffer too! + //Keep discarding new serial data until we see a gap of 2ms - hopefully indicating that the module's TX buffer is empty. + if (assumeSuccess) + { + unsigned long startTime = millis(); + unsigned long lastActivity = startTime; + bool keepGoing = true; + while (keepGoing && (millis() < (startTime + (unsigned long)maxWait))) + { + while (_serialPort->available()) // Discard any new data + { + _serialPort->read(); + lastActivity = millis(); + } + + if (millis() > (lastActivity + (unsigned long)2)) // Check if we have seen no new data for at least 2ms + keepGoing = false; + } + } + // Call isConnected up to three times - tests on the NEO-M8U show the CFG RATE poll occasionally being ignored bool connected = isConnected(maxWait); @@ -537,7 +563,7 @@ bool SFE_UBLOX_GNSS::begin(Stream &serialPort, uint16_t maxWait, bool assumeSucc connected = isConnected(maxWait); } - if ((!connected ) && assumeSuccess) // Advanced users can assume success if required. Useful if the port is outputting messages at high navigation rate. + if ((!connected ) && assumeSuccess && _signsOfLife) // Advanced users can assume success if required. Useful if the port is outputting messages at high navigation rate. { #ifndef SFE_UBLOX_REDUCED_PROG_MEM if ((_printDebug == true) || (_printLimitedDebug == true)) // This is important. Print this if doing limited debugging @@ -558,6 +584,7 @@ bool SFE_UBLOX_GNSS::begin(SPIClass &spiPort, uint8_t csPin, uint32_t spiSpeed, _spiPort = &spiPort; _csPin = csPin; _spiSpeed = spiSpeed; + _signsOfLife = false; //Clear the _signsOfLife flag. It will be set true if valid traffic is seen. // Initialize the chip select pin pinMode(_csPin, OUTPUT); @@ -617,7 +644,7 @@ bool SFE_UBLOX_GNSS::begin(SPIClass &spiPort, uint8_t csPin, uint32_t spiSpeed, connected = isConnected(maxWait); } - if ((!connected ) && assumeSuccess) // Advanced users can assume success if required. Useful if the port is outputting messages at high navigation rate. + if ((!connected ) && assumeSuccess && _signsOfLife) // Advanced users can assume success if required. Useful if the port is outputting messages at high navigation rate. { #ifndef SFE_UBLOX_REDUCED_PROG_MEM if ((_printDebug == true) || (_printLimitedDebug == true)) // This is important. Print this if doing limited debugging @@ -1637,6 +1664,11 @@ void SFE_UBLOX_GNSS::process(uint8_t incoming, ubxPacket *incomingUBX, uint8_t r if (nmeaByteCounter == 5) { + if (!_signsOfLife) // If _signsOfLife is not already true, set _signsOfLife to true if the NMEA header is valid + { + _signsOfLife = isNMEAHeaderValid(); + } + // We've just received the end of the address field. Check if it is selected for logging if (logThisNMEA()) { @@ -1710,6 +1742,38 @@ bool SFE_UBLOX_GNSS::logThisNMEA() return (false); } +// PRIVATE: Return true if the NMEA header is valid +bool SFE_UBLOX_GNSS::isNMEAHeaderValid() +{ + if (nmeaAddressField[0] != '*') return (false); + if (nmeaAddressField[1] != 'G') return (false); + if ((nmeaAddressField[3] == 'D') && (nmeaAddressField[4] == 'T') && (nmeaAddressField[5] == 'M')) return (true); + if (nmeaAddressField[3] == 'G') + { + if ((nmeaAddressField[4] == 'A') && (nmeaAddressField[5] == 'Q')) return (true); + if ((nmeaAddressField[4] == 'B') && (nmeaAddressField[5] == 'Q')) return (true); + if ((nmeaAddressField[4] == 'B') && (nmeaAddressField[5] == 'S')) return (true); + if ((nmeaAddressField[4] == 'G') && (nmeaAddressField[5] == 'A')) return (true); + if ((nmeaAddressField[4] == 'L') && (nmeaAddressField[5] == 'L')) return (true); + if ((nmeaAddressField[4] == 'L') && (nmeaAddressField[5] == 'Q')) return (true); + if ((nmeaAddressField[4] == 'N') && (nmeaAddressField[5] == 'Q')) return (true); + if ((nmeaAddressField[4] == 'N') && (nmeaAddressField[5] == 'S')) return (true); + if ((nmeaAddressField[4] == 'P') && (nmeaAddressField[5] == 'Q')) return (true); + if ((nmeaAddressField[4] == 'Q') && (nmeaAddressField[5] == 'Q')) return (true); + if ((nmeaAddressField[4] == 'R') && (nmeaAddressField[5] == 'S')) return (true); + if ((nmeaAddressField[4] == 'S') && (nmeaAddressField[5] == 'A')) return (true); + if ((nmeaAddressField[4] == 'S') && (nmeaAddressField[5] == 'T')) return (true); + if ((nmeaAddressField[4] == 'S') && (nmeaAddressField[5] == 'V')) return (true); + } + if ((nmeaAddressField[3] == 'R') && (nmeaAddressField[4] == 'L') && (nmeaAddressField[5] == 'M')) return (true); + if ((nmeaAddressField[3] == 'R') && (nmeaAddressField[4] == 'M') && (nmeaAddressField[5] == 'C')) return (true); + if ((nmeaAddressField[3] == 'T') && (nmeaAddressField[4] == 'X') && (nmeaAddressField[5] == 'T')) return (true); + if ((nmeaAddressField[3] == 'V') && (nmeaAddressField[4] == 'L') && (nmeaAddressField[5] == 'W')) return (true); + if ((nmeaAddressField[3] == 'V') && (nmeaAddressField[4] == 'T') && (nmeaAddressField[5] == 'G')) return (true); + if ((nmeaAddressField[3] == 'Z') && (nmeaAddressField[4] == 'D') && (nmeaAddressField[5] == 'A')) return (true); + return (false); +} + // PRIVATE: Return true if we should pass this NMEA message to processNMEA bool SFE_UBLOX_GNSS::processThisNMEA() { @@ -1887,6 +1951,7 @@ void SFE_UBLOX_GNSS::processUBX(uint8_t incoming, ubxPacket *incomingUBX, uint8_ if ((incomingUBX->checksumA == rollingChecksumA) && (incomingUBX->checksumB == rollingChecksumB)) { incomingUBX->valid = SFE_UBLOX_PACKET_VALIDITY_VALID; // Flag the packet as valid + _signsOfLife = true; //The checksum is valid, so set the _signsOfLife flag // Let's check if the class and ID match the requestedClass and requestedID // Remember - this could be a data packet or an ACK packet @@ -3601,7 +3666,7 @@ sfe_ublox_status_e SFE_UBLOX_GNSS::waitForACKResponse(ubxPacket *outgoingUBX, ui packetAuto.classAndIDmatch = SFE_UBLOX_PACKET_VALIDITY_NOT_DEFINED; unsigned long startTime = millis(); - while (millis() - startTime < maxTime) + while (millis() < (startTime + (unsigned long)maxTime)) { if (checkUbloxInternal(outgoingUBX, requestedClass, requestedID) == true) //See if new data is available. Process bytes as they come in. { @@ -3740,7 +3805,7 @@ sfe_ublox_status_e SFE_UBLOX_GNSS::waitForACKResponse(ubxPacket *outgoingUBX, ui } //checkUbloxInternal == true delay(1); // Allow an RTOS to get an elbow in (#11) - } //while (millis() - startTime < maxTime) + } //while (millis() < (startTime + (unsigned long)maxTime)) // We have timed out... // If the outgoingUBX->classAndIDmatch is VALID then we can take a gamble and return DATA_RECEIVED diff --git a/src/SparkFun_u-blox_GNSS_Arduino_Library.h b/src/SparkFun_u-blox_GNSS_Arduino_Library.h index 6f5bc92..4b3194b 100644 --- a/src/SparkFun_u-blox_GNSS_Arduino_Library.h +++ b/src/SparkFun_u-blox_GNSS_Arduino_Library.h @@ -599,6 +599,7 @@ class SFE_UBLOX_GNSS void setPacketCfgPayloadSize(size_t payloadSize); // Set packetCfgPayloadSize //Begin communication with the GNSS. Advanced users can assume success if required. Useful if the port is already outputting messages at high navigation rate. + //Begin will then return true if "signs of life" have been seen: reception of _any_ valid UBX packet or _any_ valid NMEA header. //By default use the default I2C address, and use Wire port bool begin(TwoWire &wirePort = Wire, uint8_t deviceAddress = 0x42, uint16_t maxWait = defaultMaxWait, bool assumeSuccess = false); //Returns true if module is detected //serialPort needs to be perviously initialized to correct baud rate @@ -1498,6 +1499,7 @@ class SFE_UBLOX_GNSS uint8_t nmeaAddressField[6]; // NMEA Address Field - includes the start character (*) bool logThisNMEA(); // Return true if we should log this NMEA message bool processThisNMEA(); // Return true if we should pass this NMEA message to processNMEA + bool isNMEAHeaderValid(); // Return true if the six byte NMEA header appears valid. Used to set _signsOfLife uint16_t rtcmLen = 0; @@ -1527,6 +1529,10 @@ class SFE_UBLOX_GNSS bool _pushSingleByte = false; uint8_t _pushThisSingleByte; + // .begin will return true if the assumeSuccess parameter is true and if _signsOfLife is true + // _signsOfLife is set to true when: a valid UBX message is seen; a valig NMEA header is seen. + bool _signsOfLife; + }; #endif