From b0f0eb520391b1c99a5cead25b5be4a8bfb6c679 Mon Sep 17 00:00:00 2001 From: Paul Date: Wed, 31 May 2023 23:15:02 +0100 Subject: [PATCH 1/4] Fix #27 --- src/u-blox_GNSS.cpp | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/src/u-blox_GNSS.cpp b/src/u-blox_GNSS.cpp index 2b537ef..8280e95 100644 --- a/src/u-blox_GNSS.cpp +++ b/src/u-blox_GNSS.cpp @@ -962,7 +962,7 @@ void DevUBLOXGNSS::setSpiBufferSize(size_t bufferSize) #ifndef SFE_UBLOX_REDUCED_PROG_MEM if (_printDebug == true) { - _debugSerial.println(F("setSpiTransactionSize: you need to call setSpiBufferSize _before_ begin!")); + _debugSerial.println(F("setSpiBufferSize: you need to call setSpiBufferSize _before_ begin!")); } #endif } @@ -6089,8 +6089,23 @@ bool DevUBLOXGNSS::pushRawData(uint8_t *dataBytes, size_t numDataBytes, bool cal bool ok = false; if (_commType == COMM_TYPE_SERIAL) { - // Serial: write all the bytes in one go - ok = writeBytes(dataBytes, numDataBytes) == numDataBytes; + // Serial: writeBytes can only write 255 bytes maximum, so we need to divide up into chunks if required + ok = true; + size_t bytesLeftToWrite = numDataBytes; + while (bytesLeftToWrite > 0) + { + uint8_t bytesToWrite; + + if (bytesLeftToWrite < 0x100) + bytesToWrite = (uint8_t)bytesLeftToWrite; + else + bytesToWrite = 0xFF; + + ok &= (writeBytes(dataBytes, bytesToWrite) == bytesToWrite); // Will set ok to false if any one write fails + + bytesLeftToWrite -= (size_t)bytesToWrite; + dataBytes += bytesToWrite; + } } else if (_commType == COMM_TYPE_I2C) { From 08b954cca02991c020da24614aef9ebdc8f9d521 Mon Sep 17 00:00:00 2001 From: Paul Date: Wed, 31 May 2023 23:15:52 +0100 Subject: [PATCH 2/4] Add Serial version of Example17 --- ..._NTRIPClient_With_GGA_Callback__Serial.ino | 570 ++++++++++++++++++ .../secrets.h | 27 + 2 files changed, 597 insertions(+) create mode 100644 examples/ZED-F9P/Example17_NTRIPClient_With_GGA_Callback__Serial/Example17_NTRIPClient_With_GGA_Callback__Serial.ino create mode 100644 examples/ZED-F9P/Example17_NTRIPClient_With_GGA_Callback__Serial/secrets.h diff --git a/examples/ZED-F9P/Example17_NTRIPClient_With_GGA_Callback__Serial/Example17_NTRIPClient_With_GGA_Callback__Serial.ino b/examples/ZED-F9P/Example17_NTRIPClient_With_GGA_Callback__Serial/Example17_NTRIPClient_With_GGA_Callback__Serial.ino new file mode 100644 index 0000000..e686a48 --- /dev/null +++ b/examples/ZED-F9P/Example17_NTRIPClient_With_GGA_Callback__Serial/Example17_NTRIPClient_With_GGA_Callback__Serial.ino @@ -0,0 +1,570 @@ +/* + 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. + + This example shows how to obtain RTCM data from a NTRIP Caster over WiFi and push it over Serial 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: + + Use jumper wires to connect: + 5V / V_USB -> ZED-F9P Breakout 5V + Serial1 TX (3.3V) -> ZED-F9P UART1 RX + Serial1 RX (3.3V) -> ZED-F9P UART1 TX + GND -> GND + + Open the serial monitor at 115200 baud to see the output +*/ + +#include +#include "secrets.h" + +#include //http://librarymanager/All#SparkFun_u-blox_GNSS_v3 +SFE_UBLOX_GNSS_SERIAL 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 + +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) +{ + double latitude = ubxDataStruct->lat; // Print the latitude + Serial.print(F("Lat: ")); + Serial.print(latitude / 10000000.0, 7); + + double longitude = ubxDataStruct->lon; // Print the longitude + Serial.print(F(" Long: ")); + Serial.print(longitude / 10000000.0, 7); + + double altitude = ubxDataStruct->hMSL; // Print the height above mean sea level + Serial.print(F(" Height: ")); + Serial.print(altitude / 1000.0, 3); + + 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(); +} + +//=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + +// Callback: printRTCMdata1005 will be called when new RTCM 1005 data has been parsed from pushRawData +// See u-blox_structs.h for the full definition of RTCM_1005_data_t +// _____ You can use any name you like for the callback. Use the same name when you call setRTCM1005InputcallbackPtr +// / _____ This _must_ be RTCM_1005_data_t +// | / _____ You can use any name you like for the struct +// | | / +// | | | +void printRTCMdata1005(RTCM_1005_data_t *rtcmData1005) +{ + double x = rtcmData1005->AntennaReferencePointECEFX; + x /= 10000.0; // Convert to m + double y = rtcmData1005->AntennaReferencePointECEFY; + y /= 10000.0; // Convert to m + double z = rtcmData1005->AntennaReferencePointECEFZ; + z /= 10000.0; // Convert to m + + Serial.print(F("NTRIP Server RTCM 1005: ARP ECEF-X: ")); + Serial.print(x, 4); // 4 decimal places + Serial.print(F(" Y: ")); + Serial.print(y, 4); // 4 decimal places + Serial.print(F(" Z: ")); + Serial.println(z, 4); // 4 decimal places +} + +//=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + +// Callback: printRTCMdata1006 will be called when new RTCM 1006 data has been parsed from pushRawData +// See u-blox_structs.h for the full definition of RTCM_1006_data_t +// _____ You can use any name you like for the callback. Use the same name when you call setRTCM1006InputcallbackPtr +// / _____ This _must_ be RTCM_1006_data_t +// | / _____ You can use any name you like for the struct +// | | / +// | | | +void printRTCMdata1006(RTCM_1006_data_t *rtcmData1006) +{ + double x = rtcmData1006->AntennaReferencePointECEFX; + x /= 10000.0; // Convert to m + double y = rtcmData1006->AntennaReferencePointECEFY; + y /= 10000.0; // Convert to m + double z = rtcmData1006->AntennaReferencePointECEFZ; + z /= 10000.0; // Convert to m + double h = rtcmData1006->AntennaHeight; + h /= 10000.0; // Convert to m + + Serial.print(F("NTRIP Server RTCM 1006: ARP ECEF-X: ")); + Serial.print(x, 4); // 4 decimal places + Serial.print(F(" Y: ")); + Serial.print(y, 4); // 4 decimal places + Serial.print(F(" Z: ")); + Serial.print(z, 4); // 4 decimal places + Serial.print(F(" Height: ")); + Serial.println(h, 4); // 4 decimal places +} + +//=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + +void setup() +{ + delay(1000); + + Serial.begin(115200); + Serial.println(F("NTRIP testing")); + + Serial1.begin(38400); //ZED-F9 modules default to 38400 baud + + while (myGNSS.begin(Serial1) == false) //Connect to the Ublox module using Wire port + { + Serial.println(F("u-blox GPS not detected. Please check wiring.")); + } + Serial.println(F("u-blox module connected")); + + myGNSS.setUART1Output(COM_TYPE_UBX | COM_TYPE_NMEA); //Set the UART1 port to output both NMEA and UBX messages + myGNSS.setUART1Input(COM_TYPE_UBX | COM_TYPE_NMEA | COM_TYPE_RTCM3); //Be sure RTCM3 input is enabled. UBX + RTCM3 is not a valid state. + + myGNSS.setI2COutput(COM_TYPE_UBX); //Set the I2C port to output UBX messages only - disable NMEA to reduce the load on the module + + 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.setNMEAGPGGAcallbackPtr(&pushGPGGA); // Set up the callback for GPGGA + + myGNSS.setVal8(UBLOX_CFG_MSGOUT_NMEA_ID_GGA_UART1, 10); // Tell the module to output GGA every 10 seconds + + myGNSS.setVal8(UBLOX_CFG_MSGOUT_NMEA_ID_GLL_UART1, 0); // Disable GLL to reduce the load on UART1 + myGNSS.setVal8(UBLOX_CFG_MSGOUT_NMEA_ID_GSA_UART1, 0); // Disable GSA to reduce the load on UART1 + myGNSS.setVal8(UBLOX_CFG_MSGOUT_NMEA_ID_GSV_UART1, 0); // Disable GSV to reduce the load on UART1 + myGNSS.setVal8(UBLOX_CFG_MSGOUT_NMEA_ID_RMC_UART1, 0); // Disable RMC to reduce the load on UART1 + myGNSS.setVal8(UBLOX_CFG_MSGOUT_NMEA_ID_VTG_UART1, 0); // Disable VTG to reduce the load on UART1 + + myGNSS.setAutoPVTcallbackPtr(&printPVTdata); // Enable automatic NAV PVT messages with callback to printPVTdata so we can watch the carrier solution go to fixed + + myGNSS.setRTCM1005InputcallbackPtr(&printRTCMdata1005); // Set up a callback to print the RTCM 1005 Antenna Reference Position from the correction data + myGNSS.setRTCM1006InputcallbackPtr(&printRTCMdata1006); // Set up a callback to print the RTCM 1006 Antenna Reference Position from the correction data + + //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 + 1]; + 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") != nullptr) //Look for '200 OK' + { + connectionResult = 200; + } + if (strstr(response, "401") != nullptr) //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 Serial + if (myGNSS.pushRawData(rtcmData, rtcmCount)) + { + Serial.print(F("Pushed ")); + Serial.print(rtcmCount); + Serial.println(F(" RTCM bytes to ZED")); + } + else + { + Serial.print(F("FAILED to push ")); + 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); +} diff --git a/examples/ZED-F9P/Example17_NTRIPClient_With_GGA_Callback__Serial/secrets.h b/examples/ZED-F9P/Example17_NTRIPClient_With_GGA_Callback__Serial/secrets.h new file mode 100644 index 0000000..9de9b71 --- /dev/null +++ b/examples/ZED-F9P/Example17_NTRIPClient_With_GGA_Callback__Serial/secrets.h @@ -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 From 7cdb203961f83662ca1667edfb380bac4fe54ce7 Mon Sep 17 00:00:00 2001 From: Paul Date: Wed, 31 May 2023 23:16:09 +0100 Subject: [PATCH 3/4] v3.0.15 --- library.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library.properties b/library.properties index 461caa2..479639c 100644 --- a/library.properties +++ b/library.properties @@ -1,5 +1,5 @@ name=SparkFun u-blox GNSS v3 -version=3.0.14 +version=3.0.15 author=SparkFun Electronics maintainer=SparkFun Electronics sentence=Library for I2C, Serial and SPI Communication with u-blox GNSS modules

From c4bbe44fc47713f6c8f4e48730e8b88a5f15095d Mon Sep 17 00:00:00 2001 From: Paul Date: Thu, 1 Jun 2023 10:22:21 +0100 Subject: [PATCH 4/4] Call checkUbloxSerial during serial pushRawData --- src/u-blox_GNSS.cpp | 16 ++++++++++------ src/u-blox_GNSS.h | 10 +++++++--- 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/src/u-blox_GNSS.cpp b/src/u-blox_GNSS.cpp index 8280e95..e3527ea 100644 --- a/src/u-blox_GNSS.cpp +++ b/src/u-blox_GNSS.cpp @@ -6072,7 +6072,7 @@ void DevUBLOXGNSS::checkCallbacks(void) // Push (e.g.) RTCM data directly to the module // Returns true if all numDataBytes were pushed successfully // Warning: this function does not check that the data is valid. It is the user's responsibility to ensure the data is valid before pushing. -bool DevUBLOXGNSS::pushRawData(uint8_t *dataBytes, size_t numDataBytes, bool callProcessSpiBuffer) +bool DevUBLOXGNSS::pushRawData(uint8_t *dataBytes, size_t numDataBytes, bool callProcessBuffer) { // Return now if numDataBytes is zero if (numDataBytes == 0) @@ -6089,22 +6089,26 @@ bool DevUBLOXGNSS::pushRawData(uint8_t *dataBytes, size_t numDataBytes, bool cal bool ok = false; if (_commType == COMM_TYPE_SERIAL) { - // Serial: writeBytes can only write 255 bytes maximum, so we need to divide up into chunks if required + // Serial: divide pushes up into 16 byte chunks and call checkUbloxSerial between pushes + // (if callProcessBuffer is true) to try and avoid data loss ok = true; size_t bytesLeftToWrite = numDataBytes; while (bytesLeftToWrite > 0) { uint8_t bytesToWrite; - if (bytesLeftToWrite < 0x100) + if (bytesLeftToWrite < 16) bytesToWrite = (uint8_t)bytesLeftToWrite; else - bytesToWrite = 0xFF; + bytesToWrite = 16; ok &= (writeBytes(dataBytes, bytesToWrite) == bytesToWrite); // Will set ok to false if any one write fails bytesLeftToWrite -= (size_t)bytesToWrite; dataBytes += bytesToWrite; + + if (callProcessBuffer) // Try and prevent data loss during large pushes by calling checkUbloxSerial between chunks + checkUbloxSerial(&packetCfg, 0, 0); // Don't call checkUbloxInternal as we already have the lock! } } else if (_commType == COMM_TYPE_I2C) @@ -6204,7 +6208,7 @@ bool DevUBLOXGNSS::pushRawData(uint8_t *dataBytes, size_t numDataBytes, bool cal bytesLeftToWrite -= bytesToWrite; // Update the totals - if (callProcessSpiBuffer) + if (callProcessBuffer) processSpiBuffer(&packetCfg, 0, 0); // This will hopefully prevent any lost data? } @@ -6324,7 +6328,7 @@ size_t DevUBLOXGNSS::pushAssistNowDataInternal(size_t offset, bool skipTime, con } else { - bool pushResult = pushRawData((uint8_t *)(dataBytes + dataPtr), packetLength + ((size_t)8), checkForAcks ? false : true); // Push the data. Don't call processSpiBuffer when using ACKs + bool pushResult = pushRawData((uint8_t *)(dataBytes + dataPtr), packetLength + ((size_t)8), checkForAcks ? false : true); // Push the data. Don't call processSpiBuffer / checkUbloxSerial when using ACKs if (pushResult) bytesPushed += packetLength + ((size_t)8); // Increment bytesPushed if the push was successful diff --git a/src/u-blox_GNSS.h b/src/u-blox_GNSS.h index 798bb51..6b40e8f 100644 --- a/src/u-blox_GNSS.h +++ b/src/u-blox_GNSS.h @@ -262,10 +262,14 @@ class DevUBLOXGNSS // Push (e.g.) RTCM or Assist Now data directly to the module // Warning: this function does not check that the data is valid. It is the user's responsibility to ensure the data is valid before pushing. // - // For SPI: callProcessSpiBuffer defaults to true and forces pushRawData to call processSpiBuffer in between push transactions. + // For SPI: callProcessBuffer defaults to true and forces pushRawData to call processSpiBuffer in between push transactions. // This is to try and prevent incoming data being 'lost' during large (bi-directional) pushes. - // If you are only pushing limited amounts of data and/or will be calling checkUblox manually, it might be advantageous to set callProcessSpiBuffer to false. - bool pushRawData(uint8_t *dataBytes, size_t numDataBytes, bool callProcessSpiBuffer = true); + // If you are only pushing limited amounts of data and/or will be calling checkUblox manually, it might be advantageous to set callProcessBuffer to false. + // + // Likewise for Serial: callProcessBuffer defaults to true and forces pushRawData to call checkUbloxSerial in between pushing data. + // This is to try and prevent incoming data being 'lost' (overflowing the serial RX buffer) during a large push. + // If you are only pushing limited amounts of data and/or will be calling checkUblox manually, it might be advantageous to set callProcessBuffer to false. + bool pushRawData(uint8_t *dataBytes, size_t numDataBytes, bool callProcessBuffer = true); #ifndef SFE_UBLOX_DISABLE_RTCM_LOGGING // RTCM parsing - used inside pushRawData protected: