diff --git a/README.md b/README.md index 9a2be98..98a4206 100644 --- a/README.md +++ b/README.md @@ -53,6 +53,10 @@ Migrating to v2.0 is easy. There are two small changes all users will need to ma If you are using the Dead Reckoning Sensor Fusion or High Dynamic Rate messages, you will need to make more small changes to your code. Please see the [dead reckoning examples](./examples/Dead_Reckoning) for more details. There is more detail available in [Theory.md](./Theory.md#migrating-your-code-to-v20) if you need it. +## AssistNowTM + +v2.1.0 of the library adds support for u-blox AssistNowTM Assisted GNSS (A-GNSS) which can dramatically reduce the time-to-first-fix. You can find further details in the [AssistNow Examples folder](./examples/AssistNow). + ## Memory Usage The u-blox GNSS library has grown considerably over the years and v2.0.8 came very close to completely filling the program memory on platforms like the ATmega328 (Arduino Uno). diff --git a/Theory.md b/Theory.md index eb8bdd3..5b67bc8 100644 --- a/Theory.md +++ b/Theory.md @@ -5,7 +5,6 @@ When the user calls one of the methods the library will poll the u-blox module f * Wait for a minimum of 25 ms between polls (configured dynamically when update rate is set) * Write 0xFD to module * Read two bytes (0xFD and 0xFE) for bytes available -* If 0x7F or 0xFF then no bytes are available * Otherwise, read number of bytes and process into NMEA, UBX, or RTCM frame. * If checksum is valid, flag frame as complete. @@ -58,6 +57,7 @@ In v2.0, the full list of messages which can be processed and logged automatical - UBX-NAV-CLOCK (0x01 0x22): Clock solution - UBX-NAV-SVIN (0x01 0x3B): Survey-in data (**only with High Precision GNSS products**) - UBX-NAV-RELPOSNED (0x01 0x3C): Relative positioning information in NED frame (**only with High Precision GNSS products**) +- UBX-NAV-AOPSTATUS (0x01 0x60): AssistNow Autonomous status - UBX-RXM-SFRBX (0x02 0x13): Broadcast navigation data subframe - UBX-RXM-RAWX (0x02 0x15): Multi-GNSS raw measurement data (**only with ADR or High Precision GNSS or Time Sync products**) - UBX-TIM-TM2 (0x0D 0x03): Time mark data diff --git a/examples/AssistNow/AssistNow_Autonomous/Example1_AssistNowAutonomous/Example1_AssistNowAutonomous.ino b/examples/AssistNow/AssistNow_Autonomous/Example1_AssistNowAutonomous/Example1_AssistNowAutonomous.ino new file mode 100644 index 0000000..d772dfc --- /dev/null +++ b/examples/AssistNow/AssistNow_Autonomous/Example1_AssistNowAutonomous/Example1_AssistNowAutonomous.ino @@ -0,0 +1,188 @@ +/* + Monitor AssistNow Autonomous data collection + By: SparkFun Electronics / Paul Clark + Date: November 29th, 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 enable and monitor AssistNow Autonomous data collection by the module. + A callback is used to monitor AssistNow Autonomous data availability for each satellite. + A second callback is used to print the AOPSTATUS status. + + If your GNSS board has battery-backup for the RAM - and all SparkFun boards do! - then you can: + wait until the module has AssistNow Autonomous data for a few satellites; + power-cycle the board; + watch how fast it gets its first fix! + + Note: this example will only work on boards which have plenty of RAM available. + The UBX-NAV-SAT information occupies several kBytes. + + Note: this example will not work on the ZED-F9P. "The ZED-F9P supports AssistNow Online only." + + Feel like supporting open source hardware? + Buy a board from SparkFun! + SparkFun Thing Plus - ESP32 WROOM: https://www.sparkfun.com/products/15663 + SparkFun GPS Breakout - ZOE-M8Q (Qwiic): https://www.sparkfun.com/products/15193 + + 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 //http://librarymanager/All#SparkFun_u-blox_GNSS +SFE_UBLOX_GNSS myGNSS; + +//=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + +// Callback: printSATdata will be called when new NAV SAT data arrives +// See u-blox_structs.h for the full definition of UBX_NAV_SAT_data_t +// _____ You can use any name you like for the callback. Use the same name when you call setAutoNAVSATcallback +// / _____ This _must_ be UBX_NAV_SAT_data_t +// | / _____ You can use any name you like for the struct +// | | / +// | | | +void printSATdata(UBX_NAV_SAT_data_t ubxDataStruct) +{ + //Serial.println(); + + Serial.print(F("UBX-NAV-SAT contains data for ")); + Serial.print(ubxDataStruct.header.numSvs); + if (ubxDataStruct.header.numSvs == 1) + Serial.println(F(" SV")); + else + Serial.println(F(" SVs")); + + uint16_t numAopAvail = 0; // Count how many SVs have AssistNow Autonomous data available + + for (uint16_t block = 0; block < ubxDataStruct.header.numSvs; block++) // For each SV + { + if (ubxDataStruct.blocks[block].flags.bits.aopAvail == 1) // If the aopAvail bit is set + numAopAvail++; // Increment the number of SVs + } + + Serial.print(F("AssistNow Autonomous data is available for ")); + Serial.print(numAopAvail); + if (numAopAvail == 1) + Serial.println(F(" SV")); + else + Serial.println(F(" SVs")); +} + +//=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + +// Callback: printAOPstatus will be called when new NAV AOPSTATUS data arrives +// See u-blox_structs.h for the full definition of UBX_NAV_AOPSTATUS_data_t +// _____ You can use any name you like for the callback. Use the same name when you call setAutoNAVAOPSTATUScallback +// / _____ This _must_ be UBX_NAV_AOPSTATUS_data_t +// | / _____ You can use any name you like for the struct +// | | / +// | | | +void printAOPstatus(UBX_NAV_AOPSTATUS_data_t ubxDataStruct) +{ + //Serial.println(); + + Serial.print(F("AOPSTATUS status is ")); + Serial.println(ubxDataStruct.status); +} + +//=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + +// 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) +{ + // Print the UBX-NAV-PVT data so we can see how quickly the fixType goes to 3D + + Serial.println(); + + long latitude = ubxDataStruct.lat; // Print the latitude + Serial.print(F("Lat: ")); + Serial.print(latitude); + + long longitude = ubxDataStruct.lon; // Print the longitude + Serial.print(F(" Long: ")); + Serial.print(longitude); + Serial.print(F(" (degrees * 10^-7)")); + + long altitude = ubxDataStruct.hMSL; // Print the height above mean sea level + Serial.print(F(" Alt: ")); + Serial.print(altitude); + Serial.print(F(" (mm)")); + + byte fixType = ubxDataStruct.fixType; // Print the fix type + Serial.print(F(" Fix: ")); + if(fixType == 0) Serial.print(F("No fix")); + 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 == 4) Serial.print(F("GNSS + Dead reckoning")); + else if(fixType == 5) Serial.print(F("Time only")); + + Serial.println(); +} + +//=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + +void setup() +{ + delay(1000); + + Serial.begin(115200); + Serial.println(F("AssistNow Example")); + + //=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + // Start I2C. Connect to the GNSS. + + Wire.begin(); //Start I2C + + //myGNSS.enableDebugging(Serial, true); // Uncomment this line to see the 'major' debug messages on Serial + + 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.saveConfigSelective(VAL_CFG_SUBSEC_IOPORT); //Save (only) the communications port settings to flash and BBR + + //=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + // Enable AssistNow Autonomous data collection. + + if (myGNSS.setAopCfg(1) == true) + { + Serial.println(F("aopCfg enabled")); + } + else + { + Serial.println(F("Could not enable aopCfg. Please check wiring. Freezing.")); + while (1); + } + + //=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + // Enable automatic UBX-NAV-SAT and UBX-NAV-AOPSTATUS messages and set up the callbacks + + myGNSS.setNavigationFrequency(1); //Produce one solution per second + + myGNSS.setAutoNAVSATcallback(&printSATdata); // Enable automatic NAV SAT messages with callback to printSATdata + myGNSS.setAutoAOPSTATUScallback(&printAOPstatus); // Enable automatic NAV AOPSTATUS messages with callback to printAOPstatus + myGNSS.setAutoPVTcallback(&printPVTdata); // Enable automatic NAV PVT messages with callback to printPVTdata +} + +//=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + +void loop() +{ + myGNSS.checkUblox(); // Check for the arrival of new data and process it. + myGNSS.checkCallbacks(); // Check if any callbacks are waiting to be processed. + + Serial.print("."); + delay(50); +} diff --git a/examples/AssistNow/AssistNow_Autonomous/Example2_AssistNowAutonomous_DatabaseRead/Example2_AssistNowAutonomous_DatabaseRead.ino b/examples/AssistNow/AssistNow_Autonomous/Example2_AssistNowAutonomous_DatabaseRead/Example2_AssistNowAutonomous_DatabaseRead.ino new file mode 100644 index 0000000..f083d15 --- /dev/null +++ b/examples/AssistNow/AssistNow_Autonomous/Example2_AssistNowAutonomous_DatabaseRead/Example2_AssistNowAutonomous_DatabaseRead.ino @@ -0,0 +1,207 @@ +/* + Read the AssistNow Autonomous database from the module + By: SparkFun Electronics / Paul Clark + Date: November 29th, 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 enable, check the status of, and read the AssistNow Autonomous data from the module. + + Note: this example will only work on boards which have plenty of RAM available. + The database can be several kBytes in length. + + Note: this example will not work on the ZED-F9P. "The ZED-F9P supports AssistNow Online only." + + Feel like supporting open source hardware? + Buy a board from SparkFun! + SparkFun Thing Plus - ESP32 WROOM: https://www.sparkfun.com/products/15663 + SparkFun GPS Breakout - ZOE-M8Q (Qwiic): https://www.sparkfun.com/products/15193 + + 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 //http://librarymanager/All#SparkFun_u-blox_GNSS +SFE_UBLOX_GNSS myGNSS; + +//=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + +// Callback: printSATdata will be called when new NAV SAT data arrives +// See u-blox_structs.h for the full definition of UBX_NAV_SAT_data_t +// _____ You can use any name you like for the callback. Use the same name when you call setAutoNAVSATcallback +// / _____ This _must_ be UBX_NAV_SAT_data_t +// | / _____ You can use any name you like for the struct +// | | / +// | | | +void printSATdata(UBX_NAV_SAT_data_t ubxDataStruct) +{ + Serial.println(); + + Serial.print(F("UBX-NAV-SAT contains data for ")); + Serial.print(ubxDataStruct.header.numSvs); + if (ubxDataStruct.header.numSvs == 1) + Serial.println(F(" SV")); + else + Serial.println(F(" SVs")); + + uint16_t numAopAvail = 0; // Count how many SVs have AssistNow Autonomous data available + + for (uint16_t block = 0; block < ubxDataStruct.header.numSvs; block++) // For each SV + { + if (ubxDataStruct.blocks[block].flags.bits.aopAvail == 1) // If the aopAvail bit is set + numAopAvail++; // Increment the number of SVs + } + + Serial.print(F("AssistNow Autonomous data is available for ")); + Serial.print(numAopAvail); + if (numAopAvail == 1) + Serial.println(F(" SV")); + else + Serial.println(F(" SVs")); +} + +//=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + +// Callback: printAOPstatus will be called when new NAV AOPSTATUS data arrives +// See u-blox_structs.h for the full definition of UBX_NAV_AOPSTATUS_data_t +// _____ You can use any name you like for the callback. Use the same name when you call setAutoNAVAOPSTATUScallback +// / _____ This _must_ be UBX_NAV_AOPSTATUS_data_t +// | / _____ You can use any name you like for the struct +// | | / +// | | | +void printAOPstatus(UBX_NAV_AOPSTATUS_data_t ubxDataStruct) +{ + //Serial.println(); + + Serial.print(F("AOPSTATUS status is ")); + Serial.println(ubxDataStruct.status); +} + +//=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + +void setup() +{ + delay(1000); + + Serial.begin(115200); + Serial.println(F("AssistNow Example")); + + while (Serial.available()) Serial.read(); // Empty the serial buffer + Serial.println(F("Press any key to begin...")); + while (!Serial.available()); // Wait for a keypress + + //=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + // Start I2C. Connect to the GNSS. + + 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.enableDebugging(Serial, true); // Uncomment this line to see helpful debug messages on Serial + + //=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + // Enable AssistNow Autonomous data collection. + + if (myGNSS.setAopCfg(1) == true) + { + Serial.println(F("aopCfg enabled")); + } + else + { + Serial.println(F("Could not enable aopCfg. Please check wiring. Freezing.")); + while (1); + } + + //=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + // Enable automatic UBX-NAV-SAT and UBX-NAV-AOPSTATUS messages and set up the callbacks + + myGNSS.setNavigationFrequency(1); //Produce one solution per second + + myGNSS.setAutoNAVSATcallback(&printSATdata); // Enable automatic NAV SAT messages with callback to printSATdata + myGNSS.setAutoAOPSTATUScallback(&printAOPstatus); // Enable automatic NAV AOPSTATUS messages with callback to printAOPstatus + + //=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + // Keep displaying NAV SAT and AOPSTATUS until the user presses a key + + Serial.println(F("AssistNow Autonomous data collection is in progress. Press any key to quit and read the database.")); + + while (Serial.available()) Serial.read(); // Empty the serial buffer + + while (!Serial.available()) // Wait for the arrival of a keypress + { + myGNSS.checkUblox(); // Check for the arrival of new data and process it. + myGNSS.checkCallbacks(); // Check if any callbacks are waiting to be processed. + + Serial.print("."); + delay(50); + } + + Serial.println(); + + //=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + // Disable the automatic UBX-NAV-SAT and UBX-NAV-AOPSTATUS messages + + myGNSS.setAutoNAVSAT(false); + myGNSS.setAutoAOPSTATUS(false); + delay(1100); + + //=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + // Read the AssistNow Autonomous database from the module and pretty-print it (so it can be copied and pasted into the next example) + + #define MAX_DATABASE_LENGTH 32768 // Allocate 32kBytes to store the navigation database + size_t maxDatabaseLen = MAX_DATABASE_LENGTH; + uint8_t *database = new uint8_t[MAX_DATABASE_LENGTH]; // The database will be stored here + + Serial.println(F("Storage has been allocated for the database.")); Serial.flush(); + + size_t actualDatabaseLen = myGNSS.readNavigationDatabase(database, maxDatabaseLen); // Read the database + + Serial.print(F("The Navigation Database length was ")); + Serial.println(actualDatabaseLen); + + if (actualDatabaseLen == maxDatabaseLen) + Serial.println(F("There was not enough memory to store the entire database. Some data will have been lost!")); + + // Pretty-print the database so it can be copied into the next example + Serial.println(F("Copy and paste the following into the next example, so you can write it back to the module:")); + Serial.println(); + Serial.print(F("size_t databaseLen = ")); + Serial.print(actualDatabaseLen); + Serial.println(F(";")); + Serial.print(F("const uint8_t database[")); + Serial.print(actualDatabaseLen); + Serial.println(F("] = {")); + size_t i; + for(i = 0; i < actualDatabaseLen; i++) + { + if ((i % 32) == 0) + Serial.print(F(" 0x")); + if (*(database + i) < 0x10) // Print leading zero + Serial.print(F("0")); + Serial.print(*(database + i), HEX); + if (i == (actualDatabaseLen - 1)) + Serial.println(); + else if ((i % 32) == 31) + Serial.println(F(",")); + else + Serial.print(F(", 0x")); + } + Serial.println(F("};")); + +} + +//=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + +void loop() +{ + // Nothing to do here +} diff --git a/examples/AssistNow/AssistNow_Autonomous/Example3_AssistNowAutonomous_DatabaseWrite/Example3_AssistNowAutonomous_DatabaseWrite.ino b/examples/AssistNow/AssistNow_Autonomous/Example3_AssistNowAutonomous_DatabaseWrite/Example3_AssistNowAutonomous_DatabaseWrite.ino new file mode 100644 index 0000000..8494530 --- /dev/null +++ b/examples/AssistNow/AssistNow_Autonomous/Example3_AssistNowAutonomous_DatabaseWrite/Example3_AssistNowAutonomous_DatabaseWrite.ino @@ -0,0 +1,188 @@ +/* + Write the AssistNow Autonomous database data to the module + By: SparkFun Electronics / Paul Clark + Date: December 1st, 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 write the AssistNow Autonomous database date back to the module. + + This example is written for the ESP32. A WiFi connection is used to get network time to pass to the module. + (You could use an RTC instead.) + + Copy and paste the database data from the previous example into database.h. + + Update secrets.h with your: + - WiFi credentials + + Note: this example will not work on the ZED-F9P. "The ZED-F9P supports AssistNow Online only." + + Note: this example works best if you have the GNSS RAM battery-backup disabled. + All SparkFun boards have battery-backup for the RAM which will means the database is retained if you disconnect the power. + The module will use the database data from the battery-backed RAM when you turn the power back on. + You will only see the improvement in the time-to-first-fix if you disable the battery first - or you are using a non-SparkFun + board that does not have the backup battery. + + Feel like supporting open source hardware? + Buy a board from SparkFun! + SparkFun Thing Plus - ESP32 WROOM: https://www.sparkfun.com/products/15663 + SparkFun GPS Breakout - ZOE-M8Q (Qwiic): https://www.sparkfun.com/products/15193 + + 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 "database.h" // <- Copy and paste the database data from the previous example into database.h + +//=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + +#include +#include +#include "secrets.h" + +//=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + +#include //http://librarymanager/All#SparkFun_u-blox_GNSS +SFE_UBLOX_GNSS myGNSS; + +//=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + +#include "time.h" + +const char* ntpServer = "pool.ntp.org"; // The Network Time Protocol Server + +//=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + +void setup() +{ + delay(1000); + + Serial.begin(115200); + Serial.println(F("AssistNow Example")); + + while (Serial.available()) Serial.read(); // Empty the serial buffer + Serial.println(F("Press any key to begin...")); + while (!Serial.available()); // Wait for a keypress + + //=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + // Start I2C. Connect to the GNSS. + + 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.enableDebugging(Serial, true); // Uncomment this line to see helpful debug messages on Serial + + //=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + // Connect to WiFi. + + Serial.print(F("Connecting to local WiFi")); + + WiFi.begin(ssid, password); + while (WiFi.status() != WL_CONNECTED) + { + delay(500); + Serial.print(F(".")); + } + Serial.println(); + + Serial.println(F("WiFi connected!")); + + //=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + // Set the RTC using network time. (Code taken from the SimpleTime example.) + + // Request the time from the NTP server and use it to set the ESP32's RTC. + configTime(0, 0, ntpServer); // Set the GMT and daylight offsets to zero. We need UTC, not local time. + + struct tm timeinfo; + if(!getLocalTime(&timeinfo)) + { + Serial.println("Failed to obtain time"); + } + else + { + Serial.println(&timeinfo, "Time is: %A, %B %d %Y %H:%M:%S"); + } + + //=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + // Push the RTC time to the module + + // Uncomment the next line to enable the 'major' debug messages on Serial so you can see what AssistNow data is being sent + //myGNSS.enableDebugging(Serial, true); + + if(getLocalTime(&timeinfo)) + { + // setUTCTimeAssistance uses a default time accuracy of 2 seconds which should be OK here. + // Have a look at the library source code for more details. + myGNSS.setUTCTimeAssistance(timeinfo.tm_year + 1900, timeinfo.tm_mon + 1, timeinfo.tm_mday, + timeinfo.tm_hour, timeinfo.tm_min, timeinfo.tm_sec); + } + else + { + Serial.println("Failed to obtain time. This will not work well. The GNSS needs accurate time to start up quickly."); + } + + //=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + // Push the AssistNow Autonomous data to the module + + size_t bytesPushed = myGNSS.pushAssistNowData(database, databaseLen); + + Serial.print(F("Pushed ")); + Serial.print(bytesPushed); + Serial.println(F(" bytes of AssistNow Autonomous data to the module")); + + if (bytesPushed != databaseLen) + Serial.println(F("Warning: bytesPushed does not match databaseLen! Maybe the database contains bad data? Or there was a communication error?")); + + //=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + // Disconnect the WiFi as it's no longer needed + + WiFi.disconnect(true); + WiFi.mode(WIFI_OFF); + Serial.println(F("WiFi disconnected")); +} + +//=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + +void loop() +{ + // Print the UBX-NAV-PVT data so we can see how quickly the fixType goes to 3D + + long latitude = myGNSS.getLatitude(); + Serial.print(F("Lat: ")); + Serial.print(latitude); + + long longitude = myGNSS.getLongitude(); + Serial.print(F(" Long: ")); + Serial.print(longitude); + Serial.print(F(" (degrees * 10^-7)")); + + long altitude = myGNSS.getAltitude(); + Serial.print(F(" Alt: ")); + Serial.print(altitude); + Serial.print(F(" (mm)")); + + byte SIV = myGNSS.getSIV(); + Serial.print(F(" SIV: ")); + Serial.print(SIV); + + byte fixType = myGNSS.getFixType(); + Serial.print(F(" Fix: ")); + if(fixType == 0) Serial.print(F("No fix")); + 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 == 4) Serial.print(F("GNSS + Dead reckoning")); + else if(fixType == 5) Serial.print(F("Time only")); + + Serial.println(); +} diff --git a/examples/AssistNow/AssistNow_Autonomous/Example3_AssistNowAutonomous_DatabaseWrite/database.h b/examples/AssistNow/AssistNow_Autonomous/Example3_AssistNowAutonomous_DatabaseWrite/database.h new file mode 100644 index 0000000..46656b9 --- /dev/null +++ b/examples/AssistNow/AssistNow_Autonomous/Example3_AssistNowAutonomous_DatabaseWrite/database.h @@ -0,0 +1,177 @@ +// Paste the AssistNow Autonomous database data from the previous example here: + +size_t databaseLen = 5480; +const uint8_t database[5480] = { + 0xB5, 0x62, 0x13, 0x80, 0x54, 0x00, 0x01, 0x00, 0x00, 0x02, 0x15, 0x0C, 0x01, 0x0D, 0x3B, 0x00, 0x04, 0x00, 0xE0, 0x4A, 0x01, 0x03, 0x01, 0x08, 0xC4, 0x04, 0x26, 0xFE, 0x02, 0x54, 0xBA, 0xA9, + 0xFF, 0xDA, 0x8C, 0x05, 0xA3, 0xC1, 0xCD, 0x85, 0x86, 0x0A, 0x14, 0x20, 0xAA, 0xE6, 0xB4, 0xD3, 0x4F, 0x27, 0x12, 0xE0, 0xE6, 0xC3, 0xE4, 0x79, 0x0E, 0xA1, 0xD3, 0x49, 0xF4, 0xFF, 0xAF, 0xF7, + 0x67, 0x2E, 0x93, 0xF9, 0x6A, 0x1B, 0x2D, 0x10, 0xD3, 0x49, 0xBD, 0xFF, 0x26, 0x01, 0x00, 0x1E, 0x81, 0x22, 0xA1, 0xE4, 0x2A, 0x00, 0x10, 0x50, 0x04, 0x00, 0xB7, 0xBF, 0xB5, 0x62, 0x13, 0x80, + 0x54, 0x00, 0x01, 0x00, 0x00, 0x05, 0x15, 0x0C, 0x01, 0x0D, 0x3B, 0x00, 0x04, 0x00, 0xE0, 0x4A, 0x01, 0x03, 0x04, 0x1E, 0xC4, 0x44, 0x72, 0x2F, 0x02, 0x71, 0xAD, 0xA4, 0xFF, 0xE8, 0xAE, 0xFA, + 0x42, 0x25, 0xD1, 0x0F, 0x0D, 0x03, 0xCB, 0x71, 0xCF, 0x12, 0x6F, 0x89, 0x0F, 0x27, 0xF3, 0x49, 0x7B, 0x29, 0x4B, 0xF1, 0x0C, 0xA1, 0xD4, 0x49, 0xF5, 0xFF, 0xB6, 0x03, 0x98, 0x36, 0xA0, 0x03, + 0xBB, 0x0F, 0x72, 0x1D, 0xD4, 0x49, 0xEE, 0xFF, 0x17, 0x00, 0x00, 0xC0, 0x85, 0x22, 0x2F, 0xF0, 0x3D, 0x00, 0x3C, 0x50, 0x04, 0x00, 0x1A, 0x35, 0xB5, 0x62, 0x13, 0x80, 0x54, 0x00, 0x01, 0x00, + 0x00, 0x07, 0x15, 0x0C, 0x01, 0x0D, 0x3B, 0x00, 0x04, 0x00, 0xE0, 0x4A, 0x01, 0x03, 0x06, 0x5B, 0xC4, 0x44, 0xD8, 0xEC, 0x02, 0x4E, 0x79, 0xAC, 0xFF, 0xE8, 0x8A, 0x1D, 0xBC, 0x9F, 0xE8, 0x86, + 0xD9, 0x07, 0x45, 0xD0, 0x05, 0x6A, 0x05, 0x06, 0xC3, 0x26, 0xB5, 0x7D, 0x4A, 0xA2, 0x86, 0xCA, 0x0D, 0xA1, 0xD4, 0x49, 0x2B, 0x00, 0x62, 0x07, 0x63, 0x2F, 0xF5, 0x05, 0x1E, 0x1A, 0x98, 0x10, + 0xD4, 0x49, 0x38, 0x00, 0xBA, 0xFF, 0x00, 0xCB, 0xBE, 0x22, 0xF4, 0x59, 0x09, 0x00, 0xB6, 0x50, 0x04, 0x00, 0x2E, 0x9B, 0xB5, 0x62, 0x13, 0x80, 0x54, 0x00, 0x01, 0x00, 0x00, 0x09, 0x15, 0x0C, + 0x01, 0x0D, 0x3B, 0x00, 0x04, 0x00, 0xE0, 0x4A, 0x01, 0x03, 0x08, 0x5E, 0xC4, 0x44, 0xFA, 0x7B, 0x02, 0x4E, 0x1B, 0xA5, 0xFF, 0x03, 0x6E, 0x07, 0xF5, 0x25, 0xF3, 0xB8, 0x36, 0x01, 0xDB, 0x4F, + 0x56, 0x3E, 0x8D, 0xA6, 0xD7, 0x26, 0x82, 0xFE, 0xA8, 0x4A, 0x7A, 0x44, 0x0D, 0xA1, 0xD4, 0x49, 0x05, 0x00, 0x7A, 0xFA, 0x1C, 0x37, 0xDC, 0xFA, 0x33, 0x0E, 0x7E, 0x1E, 0xD4, 0x49, 0xE5, 0xFF, + 0xFC, 0xFF, 0x00, 0xB7, 0xBA, 0x22, 0x87, 0x2A, 0x34, 0x00, 0xBC, 0x50, 0x04, 0x00, 0xDF, 0x11, 0xB5, 0x62, 0x13, 0x80, 0x54, 0x00, 0x01, 0x00, 0x00, 0x0B, 0x15, 0x0C, 0x01, 0x0D, 0x3B, 0x00, + 0x04, 0x00, 0xE0, 0x4A, 0x01, 0x03, 0x0A, 0x2F, 0xC4, 0x44, 0xC7, 0x68, 0x00, 0xF6, 0x09, 0xAA, 0xFF, 0xED, 0xB8, 0x38, 0x24, 0x1C, 0xE8, 0xC5, 0x30, 0x00, 0xD0, 0x23, 0x17, 0xEC, 0xF7, 0x92, + 0x2D, 0x27, 0x7C, 0x39, 0x2C, 0x6B, 0x66, 0xF5, 0x0D, 0xA1, 0xD4, 0x49, 0xEA, 0xFF, 0x27, 0xF9, 0x09, 0x2F, 0x2B, 0xFA, 0x6A, 0x1B, 0xFB, 0x0F, 0xD4, 0x49, 0x01, 0x00, 0x0F, 0x00, 0x00, 0xC1, + 0x82, 0x22, 0x2A, 0x8A, 0x80, 0xFF, 0x5E, 0x50, 0x04, 0x00, 0x52, 0xBB, 0xB5, 0x62, 0x13, 0x80, 0x54, 0x00, 0x01, 0x00, 0x00, 0x0D, 0x15, 0x0C, 0x01, 0x0D, 0x3B, 0x00, 0x04, 0x00, 0xE0, 0x4A, + 0x01, 0x03, 0x0C, 0x96, 0xC4, 0x44, 0x8E, 0xBD, 0x02, 0xE4, 0x39, 0xA6, 0xFF, 0xE8, 0x7E, 0xBE, 0x5F, 0xFE, 0xF3, 0xCA, 0xF0, 0x02, 0x5D, 0x9F, 0x84, 0x44, 0x3A, 0xC9, 0x6F, 0x27, 0xFD, 0x1B, + 0xDB, 0x27, 0x60, 0x63, 0x0D, 0xA1, 0xD4, 0x49, 0x32, 0x00, 0x4B, 0xFA, 0xA6, 0x33, 0x2C, 0xFB, 0x5C, 0x10, 0xB8, 0x1C, 0xD4, 0x49, 0x0E, 0x00, 0x1E, 0x00, 0x00, 0x0D, 0xB9, 0x22, 0x3A, 0x4D, + 0x07, 0x00, 0x2C, 0x51, 0x04, 0x00, 0x76, 0x2E, 0xB5, 0x62, 0x13, 0x80, 0x54, 0x00, 0x01, 0x00, 0x00, 0x10, 0x15, 0x0C, 0x01, 0x0D, 0x3B, 0x00, 0x04, 0x00, 0xE0, 0x4A, 0x01, 0x03, 0x0F, 0x22, + 0xC4, 0x44, 0x47, 0xD2, 0x02, 0x47, 0xEA, 0xA3, 0xFF, 0xEA, 0x53, 0xBB, 0xBD, 0x37, 0xBE, 0x0A, 0x5B, 0x06, 0x85, 0x4F, 0x13, 0x98, 0x48, 0x33, 0x95, 0x27, 0xC8, 0x3B, 0xE7, 0x1B, 0x37, 0x8F, + 0x0C, 0xA1, 0xD4, 0x49, 0xC9, 0xFF, 0x09, 0x0C, 0x98, 0x30, 0xB3, 0x0A, 0x16, 0x09, 0x14, 0x26, 0xD4, 0x49, 0xB3, 0xFF, 0x4E, 0x00, 0x00, 0xE0, 0x81, 0x22, 0x2D, 0xCD, 0x31, 0x00, 0x44, 0x50, + 0x04, 0x00, 0x39, 0x9B, 0xB5, 0x62, 0x13, 0x80, 0x54, 0x00, 0x01, 0x00, 0x00, 0x14, 0x15, 0x0C, 0x01, 0x0D, 0x3B, 0x00, 0x04, 0x00, 0xE0, 0x4A, 0x01, 0x03, 0x13, 0x1B, 0xC4, 0x44, 0x6A, 0xD2, + 0x02, 0xE9, 0x7C, 0xA3, 0xFF, 0xEE, 0xD1, 0x59, 0xEE, 0xE7, 0x56, 0x56, 0xCC, 0x02, 0xE4, 0x2C, 0x39, 0x0E, 0xB6, 0x13, 0x56, 0x26, 0xDF, 0xE5, 0x41, 0x7C, 0xDA, 0x86, 0x0D, 0xA1, 0xD4, 0x49, + 0xFE, 0xFF, 0x81, 0x04, 0x73, 0x3A, 0x0F, 0x04, 0x54, 0x10, 0x59, 0x1B, 0xD4, 0x49, 0x37, 0x00, 0xDF, 0xFF, 0x00, 0x21, 0x87, 0x22, 0x0B, 0xF8, 0x10, 0x00, 0x36, 0x50, 0x04, 0x00, 0x75, 0x76, + 0xB5, 0x62, 0x13, 0x80, 0x54, 0x00, 0x01, 0x00, 0x00, 0x1D, 0x15, 0x0C, 0x01, 0x0D, 0x3B, 0x00, 0x04, 0x00, 0xE0, 0x4A, 0x01, 0x03, 0x1C, 0x31, 0xC4, 0xC4, 0x58, 0xF9, 0x02, 0xFC, 0xD0, 0xA5, + 0xFF, 0xEA, 0x04, 0x79, 0x3B, 0x17, 0xD6, 0x48, 0xEF, 0x00, 0xB9, 0x34, 0x1C, 0xC2, 0x1E, 0x93, 0x05, 0x28, 0xA0, 0x7E, 0x9D, 0x60, 0x7C, 0x63, 0x0C, 0xA1, 0xD4, 0x49, 0xD2, 0xFF, 0x51, 0xF4, + 0xD6, 0x2D, 0xD9, 0xF5, 0x45, 0x08, 0x41, 0x27, 0xD4, 0x49, 0x23, 0x00, 0xFE, 0xFF, 0x00, 0x7C, 0xBC, 0x22, 0x15, 0x51, 0x31, 0x00, 0x62, 0x50, 0x04, 0x00, 0xEC, 0xE1, 0xB5, 0x62, 0x13, 0x80, + 0x54, 0x00, 0x01, 0x00, 0x00, 0x1E, 0x15, 0x0C, 0x01, 0x0D, 0x3B, 0x00, 0x04, 0x00, 0xE0, 0x4A, 0x01, 0x03, 0x1D, 0x22, 0xC4, 0x04, 0x53, 0x24, 0x02, 0xF4, 0x99, 0xA8, 0xFF, 0x08, 0x62, 0xE5, + 0x6F, 0x9E, 0x9C, 0x48, 0xBC, 0x02, 0xB4, 0x07, 0x9B, 0x6A, 0x21, 0x82, 0x28, 0x26, 0x78, 0x77, 0x0C, 0x90, 0x2F, 0xF9, 0x0C, 0xA1, 0xD4, 0x49, 0xE5, 0xFF, 0x34, 0x08, 0x30, 0x33, 0xC0, 0x06, + 0x34, 0x1B, 0x7C, 0x0E, 0xD4, 0x49, 0xF7, 0xFF, 0xEE, 0xFF, 0x00, 0x19, 0xBE, 0x22, 0x6F, 0xC0, 0x2F, 0x00, 0x44, 0x50, 0x04, 0x00, 0x93, 0x6D, 0xB5, 0x62, 0x13, 0x80, 0x38, 0x00, 0x01, 0x00, + 0x06, 0x07, 0x15, 0x0C, 0x01, 0x0C, 0x21, 0x00, 0x02, 0x24, 0xE0, 0x4A, 0x01, 0x03, 0xA5, 0xB9, 0xC6, 0x04, 0x16, 0x44, 0x02, 0xC6, 0xE0, 0xD9, 0x21, 0x1C, 0xC0, 0xE0, 0xF8, 0xD5, 0x3D, 0x6E, + 0x01, 0x56, 0x21, 0xC1, 0x0F, 0x39, 0xF8, 0x70, 0x2D, 0x10, 0x7F, 0xFF, 0x10, 0x38, 0xEE, 0xA5, 0x80, 0x00, 0x00, 0xE8, 0x55, 0x0C, 0x1C, 0xEB, 0xB5, 0x62, 0x13, 0x80, 0x38, 0x00, 0x01, 0x00, + 0x06, 0x0F, 0x15, 0x0C, 0x01, 0x0C, 0x21, 0x00, 0x02, 0x24, 0xE0, 0x4A, 0x01, 0x03, 0xAD, 0xB9, 0xC6, 0x04, 0x6F, 0x28, 0x02, 0x17, 0x5C, 0xB7, 0xE5, 0xF7, 0x80, 0x1F, 0x46, 0x2A, 0xDE, 0xBE, + 0xF0, 0x59, 0x45, 0x5C, 0xCE, 0x39, 0x75, 0x99, 0xF7, 0x10, 0x1B, 0x7B, 0xFF, 0x78, 0x18, 0x5D, 0xFE, 0x00, 0x00, 0xE8, 0x55, 0x07, 0xC8, 0x8F, 0xB5, 0x62, 0x13, 0x80, 0x38, 0x00, 0x01, 0x00, + 0x06, 0x10, 0x15, 0x0C, 0x01, 0x0C, 0x21, 0x00, 0x02, 0x24, 0xE0, 0x4A, 0x01, 0x03, 0xAE, 0xB9, 0xC6, 0x84, 0x26, 0x63, 0x02, 0x0C, 0x60, 0x9D, 0xDA, 0x44, 0x5E, 0xE2, 0x3C, 0x27, 0xDD, 0xED, + 0xF5, 0x3B, 0xDE, 0xC1, 0xDE, 0x39, 0xA7, 0xDD, 0xF9, 0x20, 0x11, 0x54, 0x2A, 0x80, 0xFB, 0x6C, 0x80, 0x00, 0xFF, 0xEF, 0x55, 0x06, 0x17, 0x43, 0xB5, 0x62, 0x13, 0x80, 0x38, 0x00, 0x01, 0x00, + 0x06, 0x15, 0x15, 0x0C, 0x01, 0x0C, 0x21, 0x00, 0x02, 0x24, 0xE0, 0x4A, 0x01, 0x03, 0xB3, 0xB9, 0xC6, 0x04, 0x6F, 0xC3, 0x02, 0xF0, 0xC6, 0x33, 0xC1, 0x45, 0x3F, 0x28, 0xC9, 0xBB, 0xBF, 0x1B, + 0x1B, 0xEC, 0x14, 0xF1, 0xF5, 0x39, 0x55, 0x25, 0x06, 0x30, 0x84, 0xAE, 0xC7, 0xA8, 0x89, 0x3D, 0x44, 0x00, 0xFD, 0xEF, 0x55, 0x0B, 0x89, 0xE0, 0xB5, 0x62, 0x13, 0x80, 0x38, 0x00, 0x01, 0x00, + 0x06, 0x17, 0x15, 0x0C, 0x01, 0x0C, 0x21, 0x00, 0x02, 0x24, 0xE0, 0x4A, 0x01, 0x03, 0xB5, 0xB9, 0xC6, 0x04, 0x34, 0xBC, 0x02, 0xF5, 0xBE, 0x0D, 0x15, 0xEA, 0x21, 0xA3, 0xC8, 0xDB, 0x1E, 0x3A, + 0x31, 0x5A, 0x38, 0xB6, 0x20, 0x39, 0x8B, 0x4A, 0xD9, 0x30, 0xDF, 0x66, 0xF8, 0xB8, 0x9D, 0x5B, 0x00, 0x02, 0xFF, 0xEF, 0x55, 0x0A, 0x20, 0x5A, 0xB5, 0x62, 0x13, 0x80, 0x34, 0x00, 0x02, 0x00, + 0x00, 0x01, 0x15, 0x0C, 0x11, 0x13, 0x38, 0x1C, 0x00, 0x00, 0xE0, 0x4A, 0x01, 0x03, 0x00, 0x7B, 0xB0, 0x04, 0xC5, 0xE4, 0x01, 0x73, 0x47, 0x5B, 0xAE, 0x1C, 0x18, 0x0D, 0xA1, 0x00, 0x65, 0xFD, + 0x00, 0x7B, 0x41, 0x2C, 0xEA, 0x00, 0x14, 0x16, 0x24, 0x00, 0xF1, 0x02, 0x67, 0x00, 0x06, 0xEA, 0x3F, 0x8A, 0xA4, 0x7B, 0xB5, 0x62, 0x13, 0x80, 0x34, 0x00, 0x02, 0x00, 0x00, 0x02, 0x15, 0x0C, + 0x11, 0x13, 0x38, 0x1C, 0x00, 0x00, 0xE0, 0x4A, 0x01, 0x03, 0x01, 0x7B, 0xB0, 0x44, 0x66, 0x1F, 0x03, 0xC2, 0x82, 0xA8, 0x90, 0x0E, 0x1C, 0x0E, 0xA1, 0x00, 0x4E, 0xFD, 0x00, 0x7B, 0x4F, 0x99, + 0xE6, 0x00, 0xF3, 0xEB, 0xC3, 0x00, 0xE8, 0x8A, 0x6E, 0x00, 0x5C, 0x05, 0x00, 0x8A, 0xDF, 0xF9, 0xB5, 0x62, 0x13, 0x80, 0x34, 0x00, 0x02, 0x00, 0x00, 0x03, 0x15, 0x0C, 0x11, 0x13, 0x38, 0x1C, + 0x00, 0x00, 0xE0, 0x4A, 0x01, 0x03, 0x02, 0x7B, 0xB0, 0x04, 0x96, 0x8D, 0x01, 0xEB, 0x94, 0x1F, 0x20, 0x13, 0x6B, 0x0D, 0xA1, 0x00, 0x69, 0xFD, 0x00, 0x7B, 0xC1, 0x59, 0x14, 0x00, 0x8A, 0xAC, + 0x28, 0x00, 0xDD, 0x24, 0x37, 0x00, 0xE0, 0xEF, 0x3F, 0x8A, 0x0F, 0xED, 0xB5, 0x62, 0x13, 0x80, 0x34, 0x00, 0x02, 0x00, 0x00, 0x04, 0x15, 0x0C, 0x11, 0x13, 0x38, 0x1C, 0x00, 0x00, 0xE0, 0x4A, + 0x01, 0x03, 0x03, 0x7B, 0xB0, 0x44, 0x8F, 0x51, 0x01, 0xFF, 0xCA, 0x0C, 0xA8, 0x0B, 0xD6, 0x0C, 0xA1, 0x00, 0x44, 0xFD, 0x00, 0x7B, 0x3F, 0x67, 0x40, 0x00, 0x77, 0x26, 0x80, 0x00, 0x3A, 0x7C, + 0xB2, 0x00, 0x2E, 0x07, 0x00, 0x8A, 0xD8, 0xB6, 0xB5, 0x62, 0x13, 0x80, 0x34, 0x00, 0x02, 0x00, 0x00, 0x05, 0x15, 0x0C, 0x0F, 0x0C, 0x00, 0x1C, 0x00, 0x00, 0xE0, 0x4A, 0x01, 0x03, 0x04, 0x00, + 0xB0, 0x14, 0xB8, 0x76, 0x03, 0xEA, 0xD0, 0x30, 0x92, 0x0A, 0xF1, 0x0C, 0xA1, 0x00, 0x25, 0xFD, 0xD4, 0x49, 0x71, 0xCF, 0x12, 0x00, 0x49, 0x7B, 0x29, 0x00, 0xFA, 0x42, 0x25, 0x00, 0xBE, 0xFF, + 0x3F, 0x8A, 0xD6, 0xA9, 0xB5, 0x62, 0x13, 0x80, 0x34, 0x00, 0x02, 0x00, 0x00, 0x06, 0x15, 0x0C, 0x11, 0x13, 0x38, 0x1C, 0x00, 0x00, 0xE0, 0x4A, 0x01, 0x03, 0x05, 0x7B, 0xB0, 0x44, 0xBB, 0x76, + 0x01, 0x15, 0x1F, 0x14, 0x47, 0x1C, 0xB5, 0x0C, 0xA1, 0x00, 0x63, 0xFD, 0x00, 0x7B, 0x16, 0xD6, 0xE9, 0x00, 0xEC, 0xC2, 0xD5, 0x00, 0xAB, 0x35, 0x77, 0x00, 0x8C, 0x18, 0x80, 0x8A, 0x81, 0xB1, + 0xB5, 0x62, 0x13, 0x80, 0x34, 0x00, 0x02, 0x00, 0x00, 0x07, 0x15, 0x0C, 0x0F, 0x0C, 0x00, 0x1C, 0x00, 0x00, 0xE0, 0x4A, 0x01, 0x03, 0x06, 0x00, 0xB0, 0x14, 0x33, 0xF6, 0x03, 0x49, 0x98, 0x7D, + 0xC9, 0x05, 0xCA, 0x0D, 0xA1, 0x00, 0x63, 0xFD, 0xD4, 0x49, 0xD0, 0x05, 0x6A, 0x00, 0x7D, 0x4A, 0xA2, 0x00, 0x1D, 0xBC, 0x9F, 0x00, 0x2B, 0x09, 0x00, 0x8A, 0x4B, 0xEC, 0xB5, 0x62, 0x13, 0x80, + 0x34, 0x00, 0x02, 0x00, 0x00, 0x08, 0x15, 0x0C, 0x11, 0x13, 0x38, 0x1C, 0x00, 0x00, 0xE0, 0x4A, 0x01, 0x03, 0x07, 0x7B, 0xB0, 0x04, 0x04, 0xE8, 0x01, 0xB3, 0x40, 0x38, 0xDD, 0x0E, 0x13, 0x0D, + 0xA1, 0x00, 0x22, 0xFD, 0x00, 0x7B, 0x33, 0x39, 0xBE, 0x00, 0x60, 0x6D, 0x02, 0x00, 0xAF, 0xE0, 0xC5, 0x00, 0xCF, 0x07, 0x80, 0x8A, 0x59, 0x50, 0xB5, 0x62, 0x13, 0x80, 0x34, 0x00, 0x02, 0x00, + 0x00, 0x09, 0x15, 0x0C, 0x0F, 0x0C, 0x00, 0x1C, 0x00, 0x00, 0xE0, 0x4A, 0x01, 0x03, 0x08, 0x00, 0xB0, 0x14, 0x50, 0xB2, 0x03, 0x0A, 0x6B, 0x13, 0x14, 0x07, 0x44, 0x0D, 0xA1, 0x00, 0x28, 0xFD, + 0xD4, 0x49, 0x4F, 0x56, 0x3E, 0x00, 0xFE, 0xA8, 0x4A, 0x00, 0x07, 0xF5, 0x25, 0x00, 0x85, 0x06, 0x00, 0x8A, 0x09, 0xB0, 0xB5, 0x62, 0x13, 0x80, 0x34, 0x00, 0x02, 0x00, 0x00, 0x0A, 0x15, 0x0C, + 0x11, 0x13, 0x38, 0x1C, 0x00, 0x00, 0xE0, 0x4A, 0x01, 0x03, 0x09, 0x7B, 0xB0, 0x44, 0x94, 0x64, 0x01, 0x0C, 0xB3, 0x3B, 0x06, 0x13, 0x0D, 0x0D, 0xA1, 0x00, 0x6E, 0xFD, 0x00, 0x7B, 0x68, 0x3C, + 0x14, 0x00, 0x15, 0x6E, 0x97, 0x00, 0x53, 0x99, 0x16, 0x00, 0xEF, 0xF6, 0xBF, 0x8A, 0xC1, 0xA9, 0xB5, 0x62, 0x13, 0x80, 0x34, 0x00, 0x02, 0x00, 0x00, 0x0B, 0x15, 0x0C, 0x11, 0x13, 0x38, 0x1C, + 0x00, 0x00, 0xE0, 0x4A, 0x01, 0x03, 0x0A, 0x7B, 0xB0, 0x04, 0xF5, 0x0B, 0x01, 0xDE, 0x10, 0x03, 0x70, 0x0C, 0x97, 0x0D, 0xA1, 0xFF, 0x52, 0xFD, 0x00, 0x7B, 0x7A, 0x06, 0xEC, 0x00, 0xA3, 0x44, + 0x6B, 0x00, 0xA1, 0xEB, 0xC8, 0x00, 0x11, 0xF8, 0xBF, 0x8A, 0xB4, 0x35, 0xB5, 0x62, 0x13, 0x80, 0x34, 0x00, 0x02, 0x00, 0x00, 0x0C, 0x15, 0x0C, 0x11, 0x13, 0x38, 0x1C, 0x00, 0x00, 0xE0, 0x4A, + 0x01, 0x03, 0x0B, 0x7B, 0xB0, 0x04, 0xFD, 0x31, 0x01, 0x09, 0xE0, 0x44, 0xFA, 0x12, 0xD4, 0x0C, 0xA1, 0x00, 0x54, 0xFD, 0x00, 0x7B, 0xBC, 0x3F, 0x97, 0x00, 0x0C, 0x17, 0x32, 0x00, 0x5D, 0xE0, + 0x30, 0x00, 0x72, 0xF7, 0xBF, 0x8A, 0x91, 0x2E, 0xB5, 0x62, 0x13, 0x80, 0x34, 0x00, 0x02, 0x00, 0x00, 0x0D, 0x15, 0x0C, 0x0F, 0x0C, 0x00, 0x1C, 0x00, 0x00, 0xE0, 0x4A, 0x01, 0x03, 0x0C, 0x00, + 0xB0, 0x14, 0x82, 0xD3, 0x03, 0x6F, 0x0C, 0x2F, 0x96, 0x10, 0x63, 0x0D, 0xA1, 0x00, 0x31, 0xFD, 0xD4, 0x49, 0x9F, 0x84, 0x44, 0x00, 0x1B, 0xDB, 0x27, 0x00, 0xBE, 0x5F, 0xFE, 0x00, 0xE9, 0x08, + 0x00, 0x8A, 0x4A, 0x74, 0xB5, 0x62, 0x13, 0x80, 0x34, 0x00, 0x02, 0x00, 0x00, 0x0E, 0x15, 0x0C, 0x11, 0x13, 0x38, 0x1C, 0x00, 0x00, 0xE0, 0x4A, 0x01, 0x03, 0x0D, 0x7B, 0xB0, 0x44, 0x3E, 0xB1, + 0x01, 0x66, 0xD7, 0x09, 0x30, 0x08, 0xCD, 0x0C, 0xA1, 0x00, 0x4A, 0xFD, 0x00, 0x7B, 0x72, 0x05, 0x96, 0x00, 0x85, 0x34, 0x7E, 0x00, 0xFE, 0xFB, 0x2F, 0x00, 0xCC, 0xF7, 0xBF, 0x8A, 0x3C, 0x14, + 0xB5, 0x62, 0x13, 0x80, 0x34, 0x00, 0x02, 0x00, 0x00, 0x0F, 0x15, 0x0C, 0x11, 0x13, 0x38, 0x1C, 0x00, 0x00, 0xE0, 0x4A, 0x01, 0x03, 0x0E, 0x7B, 0xB0, 0x44, 0x92, 0xB0, 0x01, 0xBF, 0xF2, 0x71, + 0x4F, 0xF7, 0x0E, 0x0D, 0xA1, 0x00, 0x26, 0xFD, 0x00, 0x7B, 0x66, 0x06, 0x3A, 0x00, 0xAE, 0x39, 0x2B, 0x00, 0xEF, 0x2D, 0x9C, 0x00, 0x95, 0x0F, 0x80, 0x8A, 0x3F, 0x35, 0xB5, 0x62, 0x13, 0x80, + 0x34, 0x00, 0x02, 0x00, 0x00, 0x10, 0x15, 0x0C, 0x0F, 0x0C, 0x00, 0x1C, 0x00, 0x00, 0xE0, 0x4A, 0x01, 0x03, 0x0F, 0x00, 0xB0, 0x14, 0x96, 0x34, 0x03, 0x8E, 0xB0, 0x65, 0xEC, 0x12, 0x8F, 0x0C, + 0xA1, 0x00, 0x1F, 0xFD, 0xD4, 0x49, 0x4F, 0x13, 0x98, 0x00, 0x3B, 0xE7, 0x1B, 0x00, 0xBB, 0xBD, 0x37, 0x00, 0x39, 0xF6, 0x3F, 0x8A, 0xF3, 0x43, 0xB5, 0x62, 0x13, 0x80, 0x34, 0x00, 0x02, 0x00, + 0x00, 0x11, 0x15, 0x0C, 0x11, 0x13, 0x38, 0x1C, 0x00, 0x00, 0xE0, 0x4A, 0x01, 0x03, 0x10, 0x7B, 0xB0, 0x04, 0x5E, 0xE6, 0x01, 0xF3, 0xDF, 0x70, 0xA4, 0x18, 0x50, 0x0D, 0xA1, 0x00, 0x34, 0xFD, + 0x00, 0x7B, 0x86, 0x84, 0xC1, 0x00, 0x0F, 0xBA, 0xC1, 0x00, 0xF9, 0x59, 0xC2, 0x00, 0x39, 0x0A, 0x80, 0x8A, 0x83, 0x21, 0xB5, 0x62, 0x13, 0x80, 0x34, 0x00, 0x02, 0x00, 0x00, 0x12, 0x15, 0x0C, + 0x11, 0x13, 0x38, 0x1C, 0x00, 0x00, 0xE0, 0x4A, 0x01, 0x03, 0x11, 0x7B, 0xB0, 0x04, 0x92, 0xB3, 0x01, 0xCD, 0x23, 0x11, 0x1A, 0x12, 0xC7, 0x0C, 0xA1, 0x00, 0x57, 0xFD, 0x00, 0x7B, 0x1B, 0x89, + 0xEA, 0x00, 0x3E, 0x35, 0x7D, 0x00, 0x7D, 0xB8, 0x6F, 0x00, 0x2B, 0xF1, 0xBF, 0x8A, 0x1F, 0x89, 0xB5, 0x62, 0x13, 0x80, 0x34, 0x00, 0x02, 0x00, 0x00, 0x13, 0x15, 0x0C, 0x11, 0x13, 0x38, 0x1C, + 0x00, 0x00, 0xE0, 0x4A, 0x01, 0x03, 0x12, 0x7B, 0xB0, 0x44, 0xDE, 0x29, 0x01, 0x15, 0x35, 0x49, 0xCA, 0x17, 0xF3, 0x0C, 0xA1, 0x00, 0x30, 0xFD, 0x00, 0x7B, 0xBE, 0x59, 0xC3, 0x00, 0xEA, 0xA7, + 0x50, 0x00, 0xBF, 0x3E, 0x23, 0x00, 0x5C, 0x08, 0x80, 0x8A, 0x31, 0x50, 0xB5, 0x62, 0x13, 0x80, 0x34, 0x00, 0x02, 0x00, 0x00, 0x14, 0x15, 0x0C, 0x0F, 0x0C, 0x00, 0x1C, 0x00, 0x00, 0xE0, 0x4A, + 0x01, 0x03, 0x13, 0x00, 0xB0, 0x14, 0x3E, 0x72, 0x03, 0xA1, 0xC5, 0x2C, 0xFA, 0xFE, 0x86, 0x0D, 0xA1, 0x00, 0x1B, 0xFD, 0xD4, 0x49, 0x2C, 0x39, 0x0E, 0x00, 0xE5, 0x41, 0x7C, 0x00, 0x59, 0xEE, + 0xE7, 0x00, 0x1F, 0xFA, 0x3F, 0x8A, 0x05, 0x19, 0xB5, 0x62, 0x13, 0x80, 0x34, 0x00, 0x02, 0x00, 0x00, 0x15, 0x15, 0x0C, 0x11, 0x13, 0x38, 0x1C, 0x00, 0x00, 0xE0, 0x4A, 0x01, 0x03, 0x14, 0x7B, + 0xB0, 0x44, 0x9E, 0xFA, 0x01, 0xD6, 0x02, 0xC7, 0xCD, 0x0A, 0x8B, 0x0C, 0xA1, 0x00, 0x4C, 0xFD, 0x00, 0x7B, 0x38, 0x8D, 0xE6, 0x00, 0x0D, 0x36, 0xD5, 0x00, 0xBA, 0x5E, 0xC7, 0x00, 0xA0, 0x00, + 0x80, 0x8A, 0x7F, 0x6E, 0xB5, 0x62, 0x13, 0x80, 0x34, 0x00, 0x02, 0x00, 0x00, 0x16, 0x15, 0x0C, 0x11, 0x13, 0x38, 0x1C, 0x00, 0x00, 0xE0, 0x4A, 0x01, 0x03, 0x15, 0x7B, 0xB0, 0x44, 0x9E, 0x17, + 0x01, 0x24, 0x81, 0x37, 0xE8, 0xFC, 0xEA, 0x0C, 0xA1, 0x00, 0x4C, 0xFD, 0x00, 0x7B, 0xDE, 0x06, 0x10, 0x00, 0xD1, 0x69, 0xDC, 0x00, 0xC5, 0x18, 0x94, 0x00, 0x28, 0x16, 0x80, 0x8A, 0xBE, 0x20, + 0xB5, 0x62, 0x13, 0x80, 0x34, 0x00, 0x02, 0x00, 0x00, 0x17, 0x15, 0x0C, 0x11, 0x13, 0x38, 0x1C, 0x00, 0x00, 0xE0, 0x4A, 0x01, 0x03, 0x16, 0x7B, 0xB0, 0x44, 0x2E, 0xDB, 0x01, 0x43, 0xB7, 0x0F, + 0xCC, 0x0F, 0x25, 0x0D, 0xA1, 0x00, 0x68, 0xFD, 0x00, 0x7B, 0x32, 0x3D, 0x13, 0x00, 0x85, 0x6C, 0x73, 0x00, 0xE0, 0xD5, 0x4F, 0x00, 0x1B, 0xF8, 0xBF, 0x8A, 0x13, 0xA5, 0xB5, 0x62, 0x13, 0x80, + 0x34, 0x00, 0x02, 0x00, 0x00, 0x18, 0x15, 0x0C, 0x11, 0x13, 0x38, 0x1C, 0x00, 0x00, 0xE0, 0x4A, 0x01, 0x03, 0x17, 0x7B, 0xB0, 0x04, 0xAC, 0x23, 0x01, 0x3F, 0x2F, 0x63, 0xE2, 0xFA, 0x29, 0x0D, + 0xA1, 0x00, 0x4A, 0xFD, 0x00, 0x7B, 0x90, 0xA3, 0x66, 0x00, 0x3C, 0x04, 0x20, 0x00, 0x7B, 0x4A, 0x74, 0x00, 0x1E, 0x09, 0x80, 0x8A, 0x67, 0xDD, 0xB5, 0x62, 0x13, 0x80, 0x34, 0x00, 0x02, 0x00, + 0x00, 0x19, 0x15, 0x0C, 0x11, 0x13, 0x38, 0x1C, 0x00, 0x00, 0xE0, 0x4A, 0x01, 0x03, 0x18, 0x7B, 0xB0, 0x04, 0xF3, 0x84, 0x01, 0x2A, 0xEE, 0x52, 0xCB, 0x0A, 0x9F, 0x0D, 0xA1, 0x00, 0x4E, 0xFD, + 0x00, 0x7B, 0xFC, 0x28, 0x94, 0x00, 0x03, 0x3B, 0x27, 0x00, 0x5D, 0xC2, 0x28, 0x00, 0x06, 0x11, 0x00, 0x8A, 0xBF, 0x71, 0xB5, 0x62, 0x13, 0x80, 0x34, 0x00, 0x02, 0x00, 0x00, 0x1A, 0x15, 0x0C, + 0x11, 0x13, 0x38, 0x1C, 0x00, 0x00, 0xE0, 0x4A, 0x01, 0x03, 0x19, 0x7B, 0xB0, 0x44, 0x12, 0x8B, 0x01, 0xA4, 0xBC, 0x35, 0x56, 0xFE, 0xF0, 0x0C, 0xA1, 0x00, 0x43, 0xFD, 0x00, 0x7B, 0x47, 0x4D, + 0x92, 0x00, 0xCE, 0xEF, 0x0C, 0x00, 0x55, 0x6B, 0x0E, 0x00, 0xA9, 0x08, 0x00, 0x8A, 0x09, 0xC6, 0xB5, 0x62, 0x13, 0x80, 0x34, 0x00, 0x02, 0x00, 0x00, 0x1B, 0x15, 0x0C, 0x11, 0x13, 0x38, 0x1C, + 0x00, 0x00, 0xE0, 0x4A, 0x01, 0x03, 0x1A, 0x7B, 0xB0, 0x44, 0x25, 0xAF, 0x01, 0xD4, 0x10, 0x50, 0x81, 0x14, 0xC4, 0x0C, 0xA1, 0x00, 0x29, 0xFD, 0x00, 0x7B, 0xA9, 0xFD, 0xBE, 0x00, 0x17, 0x91, + 0x19, 0x00, 0x63, 0x98, 0xC1, 0x00, 0x12, 0xFF, 0x3F, 0x8A, 0x9F, 0x4C, 0xB5, 0x62, 0x13, 0x80, 0x34, 0x00, 0x02, 0x00, 0x00, 0x1D, 0x15, 0x0C, 0x0F, 0x0C, 0x00, 0x1C, 0x00, 0x00, 0xE0, 0x4A, + 0x01, 0x03, 0x1C, 0x00, 0xB0, 0x14, 0x3D, 0x4B, 0x03, 0x1C, 0xF4, 0x0E, 0xF2, 0x19, 0x63, 0x0C, 0xA1, 0x00, 0x2E, 0xFD, 0xD4, 0x49, 0x34, 0x1C, 0xC2, 0x00, 0x7E, 0x9D, 0x60, 0x00, 0x79, 0x3B, + 0x17, 0x00, 0x2A, 0xF6, 0x3F, 0x8A, 0x99, 0x1E, 0xB5, 0x62, 0x13, 0x80, 0x34, 0x00, 0x02, 0x00, 0x00, 0x1E, 0x15, 0x0C, 0x0F, 0x0C, 0x00, 0x1C, 0x00, 0x00, 0xE0, 0x4A, 0x01, 0x03, 0x1D, 0x00, + 0xB0, 0x14, 0x9C, 0x70, 0x03, 0x50, 0xC4, 0x2B, 0x21, 0xFC, 0xF9, 0x0C, 0xA1, 0x00, 0x44, 0xFD, 0xD4, 0x49, 0x07, 0x9B, 0x6A, 0x00, 0x77, 0x0C, 0x90, 0x00, 0xE5, 0x6F, 0x9E, 0x00, 0xF8, 0xFD, + 0x3F, 0x8A, 0x8C, 0x9F, 0xB5, 0x62, 0x13, 0x80, 0x34, 0x00, 0x02, 0x00, 0x00, 0x1F, 0x15, 0x0C, 0x11, 0x13, 0x38, 0x1C, 0x00, 0x00, 0xE0, 0x4A, 0x01, 0x03, 0x1E, 0x7B, 0xB0, 0x04, 0x9F, 0xDA, + 0x01, 0x5F, 0x6C, 0x55, 0x80, 0x08, 0xD4, 0x0C, 0xA1, 0x00, 0x5E, 0xFD, 0x00, 0x7B, 0xCB, 0xAE, 0x6A, 0x00, 0x9B, 0x04, 0x0E, 0x00, 0x96, 0x65, 0x3A, 0x00, 0x5F, 0x07, 0x80, 0x8A, 0xAA, 0xFA, + 0xB5, 0x62, 0x13, 0x80, 0x34, 0x00, 0x02, 0x00, 0x00, 0x20, 0x15, 0x0C, 0x11, 0x13, 0x38, 0x1C, 0x00, 0x00, 0xE0, 0x4A, 0x01, 0x03, 0x1F, 0x7B, 0xB0, 0x44, 0x7E, 0x41, 0x01, 0x92, 0xEF, 0x2A, + 0x99, 0x09, 0x6C, 0x0D, 0xA1, 0x00, 0x40, 0xFD, 0x00, 0x7B, 0x3C, 0xB6, 0x3E, 0x00, 0x0C, 0xA2, 0x9E, 0x00, 0x27, 0x24, 0xDF, 0x00, 0xE0, 0xFF, 0xBF, 0x8A, 0xEB, 0xFB, 0xB5, 0x62, 0x13, 0x80, + 0x24, 0x00, 0x02, 0x00, 0x01, 0x7B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0x4A, 0x01, 0x03, 0x23, 0x00, 0xB0, 0x44, 0x55, 0xF1, 0x01, 0x0A, 0x01, 0xB6, 0x8F, 0x10, 0x40, 0x8D, + 0x04, 0x00, 0x8A, 0x08, 0x0E, 0x00, 0x92, 0x02, 0xB5, 0x62, 0x13, 0x80, 0x24, 0x00, 0x02, 0x00, 0x01, 0x88, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0x4A, 0x01, 0x03, 0x30, 0x00, + 0xB0, 0x44, 0x66, 0xC5, 0x03, 0x31, 0x1B, 0xBF, 0xC2, 0x02, 0x40, 0x8D, 0x04, 0x00, 0x8A, 0x08, 0x0E, 0x00, 0x02, 0x61, 0xB5, 0x62, 0x13, 0x80, 0x2C, 0x00, 0x02, 0x00, 0x06, 0x01, 0x15, 0x0C, + 0x15, 0x15, 0x00, 0x2A, 0x00, 0x00, 0xE0, 0x4A, 0x01, 0x03, 0x9F, 0xBD, 0xD2, 0x04, 0x7B, 0x94, 0x00, 0x0C, 0xC5, 0x40, 0x6B, 0x08, 0x38, 0xC3, 0x24, 0xFD, 0xC5, 0xC3, 0xA5, 0x57, 0xE1, 0x21, + 0x3E, 0x01, 0x58, 0x1D, 0x78, 0x3A, 0x38, 0x13, 0xB5, 0x62, 0x13, 0x80, 0x2C, 0x00, 0x02, 0x00, 0x06, 0x02, 0x15, 0x0C, 0x15, 0x15, 0x00, 0x2A, 0x00, 0x00, 0xE0, 0x4A, 0x01, 0x03, 0xA0, 0xBD, + 0xB2, 0x04, 0x91, 0x39, 0x00, 0x56, 0xDD, 0x3F, 0x2B, 0x17, 0x59, 0xC0, 0x82, 0xEF, 0x6C, 0x9A, 0xA8, 0x57, 0x7C, 0x9D, 0x72, 0x07, 0xBA, 0x27, 0x78, 0x3A, 0xB1, 0x11, 0xB5, 0x62, 0x13, 0x80, + 0x2C, 0x00, 0x02, 0x00, 0x06, 0x03, 0x15, 0x0C, 0x15, 0x15, 0x00, 0x2A, 0x00, 0x00, 0xE0, 0x4A, 0x01, 0x03, 0xA1, 0xBD, 0xD2, 0x04, 0xBE, 0x31, 0x00, 0xB5, 0xD6, 0x3F, 0x6B, 0x19, 0x87, 0xDB, + 0x80, 0xFE, 0x42, 0x0C, 0xAB, 0x57, 0xDE, 0xA0, 0x21, 0x07, 0xF9, 0x23, 0x74, 0x3A, 0x7E, 0x46, 0xB5, 0x62, 0x13, 0x80, 0x2C, 0x00, 0x02, 0x00, 0x06, 0x04, 0x15, 0x0C, 0x15, 0x15, 0x00, 0x2A, + 0x00, 0x00, 0xE0, 0x4A, 0x01, 0x03, 0xA2, 0xBD, 0xB2, 0x04, 0x57, 0x31, 0x00, 0x4B, 0xBE, 0x3F, 0xAB, 0x21, 0x5E, 0x0B, 0x3F, 0xFC, 0x75, 0x74, 0xAD, 0x57, 0x5F, 0xB2, 0x07, 0x03, 0x1B, 0x2B, + 0x70, 0x3A, 0xBB, 0x0F, 0xB5, 0x62, 0x13, 0x80, 0x2C, 0x00, 0x02, 0x00, 0x06, 0x05, 0x15, 0x0C, 0x15, 0x15, 0x00, 0x2A, 0x00, 0x00, 0xE0, 0x4A, 0x01, 0x03, 0xA3, 0xBD, 0xD2, 0x04, 0x33, 0x11, + 0x00, 0x49, 0xAC, 0x3F, 0x6B, 0x28, 0xE3, 0x3A, 0x7D, 0xFD, 0x5F, 0xDB, 0xAF, 0x57, 0x9D, 0x88, 0x4D, 0x02, 0x72, 0x2B, 0x6C, 0x3A, 0x3E, 0x71, 0xB5, 0x62, 0x13, 0x80, 0x2C, 0x00, 0x02, 0x00, + 0x06, 0x06, 0x15, 0x0C, 0x15, 0x15, 0x00, 0x2A, 0x00, 0x00, 0xE0, 0x4A, 0x01, 0x03, 0xA4, 0xBD, 0xD2, 0x04, 0xEC, 0x1E, 0x00, 0xD0, 0xF5, 0x3F, 0x2B, 0x37, 0x2E, 0xF3, 0xBA, 0x80, 0x9A, 0xA6, + 0xB2, 0x57, 0xF7, 0x5D, 0x55, 0x03, 0xF4, 0x1C, 0x68, 0x3A, 0x19, 0x49, 0xB5, 0x62, 0x13, 0x80, 0x2C, 0x00, 0x02, 0x00, 0x06, 0x07, 0x15, 0x0C, 0x15, 0x15, 0x00, 0x2A, 0x00, 0x00, 0xE0, 0x4A, + 0x01, 0x03, 0xA5, 0xBD, 0xD2, 0x84, 0xCD, 0x4A, 0x02, 0x75, 0xE1, 0x3F, 0x6B, 0x39, 0x75, 0x51, 0x48, 0x81, 0x45, 0x3F, 0xA1, 0x57, 0x5D, 0xA0, 0x16, 0x06, 0x27, 0x24, 0x84, 0x3A, 0xA8, 0x47, + 0xB5, 0x62, 0x13, 0x80, 0x2C, 0x00, 0x02, 0x00, 0x06, 0x08, 0x15, 0x0C, 0x15, 0x15, 0x00, 0x2A, 0x00, 0x00, 0xE0, 0x4A, 0x01, 0x03, 0xA6, 0xBD, 0xD2, 0x04, 0x3F, 0xAB, 0x00, 0xC5, 0xCE, 0x3F, + 0xAB, 0x41, 0x2F, 0x8F, 0x06, 0x82, 0x68, 0x8F, 0xA3, 0x57, 0xB1, 0xB2, 0x4E, 0x07, 0x51, 0x24, 0x90, 0x3A, 0x81, 0x71, 0xB5, 0x62, 0x13, 0x80, 0x2C, 0x00, 0x02, 0x00, 0x06, 0x09, 0x15, 0x0C, + 0x15, 0x15, 0x00, 0x2A, 0x00, 0x00, 0xE0, 0x4A, 0x01, 0x03, 0xA7, 0xBD, 0xD2, 0x04, 0xEF, 0x93, 0x00, 0x15, 0x7A, 0x40, 0xAB, 0x4F, 0xD9, 0xD5, 0x4F, 0xFF, 0xD6, 0x42, 0xA5, 0x57, 0xAC, 0x90, + 0x39, 0x06, 0x3F, 0x11, 0x94, 0x3B, 0xA2, 0xFC, 0xB5, 0x62, 0x13, 0x80, 0x2C, 0x00, 0x02, 0x00, 0x06, 0x0A, 0x15, 0x0C, 0x15, 0x15, 0x00, 0x2A, 0x00, 0x00, 0xE0, 0x4A, 0x01, 0x03, 0xA8, 0xBD, + 0xD2, 0x84, 0x00, 0xC3, 0x00, 0xBA, 0x16, 0x40, 0x6B, 0x56, 0xF5, 0x1B, 0x8E, 0x82, 0xD8, 0x8B, 0xA7, 0x57, 0x1B, 0x06, 0xE9, 0x06, 0x2D, 0x21, 0x94, 0x3B, 0x71, 0x55, 0xB5, 0x62, 0x13, 0x80, + 0x2C, 0x00, 0x02, 0x00, 0x06, 0x0B, 0x15, 0x0C, 0x15, 0x15, 0x00, 0x2A, 0x00, 0x00, 0xE0, 0x4A, 0x01, 0x03, 0xA9, 0xBD, 0xD2, 0x04, 0xD3, 0x18, 0x00, 0x90, 0x2C, 0x40, 0x2B, 0x58, 0xCF, 0x2D, + 0x8C, 0x7E, 0xEF, 0x12, 0xAA, 0x57, 0xD2, 0xB1, 0xAD, 0x01, 0x45, 0x27, 0x94, 0x3B, 0x8F, 0x20, 0xB5, 0x62, 0x13, 0x80, 0x2C, 0x00, 0x02, 0x00, 0x06, 0x0C, 0x15, 0x0C, 0x15, 0x15, 0x00, 0x2A, + 0x00, 0x00, 0xE0, 0x4A, 0x01, 0x03, 0xAA, 0xBD, 0xD2, 0x04, 0x7F, 0x3D, 0x00, 0x5E, 0x7A, 0x40, 0xEB, 0x67, 0x2C, 0x45, 0xCA, 0xF6, 0x48, 0xA8, 0xAC, 0x57, 0xF8, 0xD5, 0x66, 0x04, 0x36, 0x22, + 0x94, 0x3B, 0x5B, 0x01, 0xB5, 0x62, 0x13, 0x80, 0x2C, 0x00, 0x02, 0x00, 0x06, 0x0D, 0x15, 0x0C, 0x15, 0x15, 0x00, 0x2A, 0x00, 0x00, 0xE0, 0x4A, 0x01, 0x03, 0xAB, 0xBD, 0xD2, 0x84, 0xC1, 0x1E, + 0x00, 0xC6, 0xFF, 0x3F, 0xAB, 0x6F, 0xFE, 0x69, 0x28, 0x82, 0xE4, 0x06, 0xAF, 0x57, 0x3D, 0x34, 0x38, 0x02, 0xFB, 0x20, 0x94, 0x3B, 0xC8, 0xB3, 0xB5, 0x62, 0x13, 0x80, 0x2C, 0x00, 0x02, 0x00, + 0x06, 0x0E, 0x15, 0x0C, 0x15, 0x15, 0x00, 0x2A, 0x00, 0x00, 0xE0, 0x4A, 0x01, 0x03, 0xAC, 0xBD, 0xB2, 0x04, 0x41, 0xAE, 0x00, 0xFC, 0x51, 0x40, 0x6B, 0x76, 0x2D, 0x7B, 0xA6, 0xFD, 0x9C, 0x9F, + 0xB1, 0x57, 0xDA, 0xAF, 0xDE, 0x01, 0xB5, 0x1B, 0x98, 0x3B, 0x8D, 0x75, 0xB5, 0x62, 0x13, 0x80, 0x2C, 0x00, 0x02, 0x00, 0x06, 0x0F, 0x15, 0x0C, 0x15, 0x15, 0x00, 0x2A, 0x00, 0x00, 0xE0, 0x4A, + 0x01, 0x03, 0xAD, 0xBD, 0xD2, 0x04, 0x32, 0x99, 0x02, 0x17, 0x61, 0x40, 0x2B, 0x78, 0x76, 0xB4, 0xD3, 0xFC, 0x10, 0x45, 0xA0, 0x57, 0x67, 0xC7, 0xEF, 0x02, 0x93, 0x20, 0x90, 0x3B, 0xC3, 0x1E, + 0xB5, 0x62, 0x13, 0x80, 0x2C, 0x00, 0x02, 0x00, 0x06, 0x10, 0x15, 0x0C, 0x15, 0x15, 0x00, 0x2A, 0x00, 0x00, 0xE0, 0x4A, 0x01, 0x03, 0xAE, 0xBD, 0xD2, 0x04, 0x70, 0x64, 0x02, 0x10, 0xC5, 0x40, + 0xEB, 0x87, 0x72, 0x87, 0xF1, 0x80, 0x8E, 0xF2, 0xA2, 0x57, 0x57, 0x0A, 0x76, 0x0C, 0xD9, 0x17, 0x88, 0x3B, 0x91, 0xAA, 0xB5, 0x62, 0x13, 0x80, 0x2C, 0x00, 0x02, 0x00, 0x06, 0x11, 0x15, 0x0C, + 0x15, 0x15, 0x00, 0x2A, 0x00, 0x00, 0xE0, 0x4A, 0x01, 0x03, 0xAF, 0xBD, 0xD2, 0x04, 0x95, 0xB0, 0x00, 0x24, 0x2D, 0x40, 0x2B, 0x89, 0x3B, 0x4D, 0x5B, 0xF0, 0xAB, 0x5C, 0xA4, 0x57, 0x50, 0x95, + 0x23, 0x04, 0xB1, 0x3A, 0x0C, 0x3A, 0x59, 0x64, 0xB5, 0x62, 0x13, 0x80, 0x2C, 0x00, 0x02, 0x00, 0x06, 0x12, 0x15, 0x0C, 0x15, 0x15, 0x00, 0x2A, 0x00, 0x00, 0xE0, 0x4A, 0x01, 0x03, 0xB0, 0xBD, + 0xD2, 0x04, 0x61, 0xBD, 0x00, 0xC7, 0x14, 0x40, 0x6B, 0x97, 0x82, 0x8D, 0xD9, 0xFC, 0xB9, 0xB4, 0xA6, 0x57, 0x71, 0xE6, 0x0F, 0x05, 0x96, 0x40, 0x10, 0x3A, 0xD3, 0x7B, 0xB5, 0x62, 0x13, 0x80, + 0x2C, 0x00, 0x02, 0x00, 0x06, 0x13, 0x15, 0x0C, 0x15, 0x15, 0x00, 0x2A, 0x00, 0x00, 0xE0, 0x4A, 0x01, 0x03, 0xB1, 0xBD, 0xD2, 0x04, 0x61, 0x35, 0x00, 0xC3, 0xC6, 0x3F, 0xEB, 0x98, 0x44, 0x01, + 0x98, 0x85, 0xA8, 0xE9, 0xA8, 0x57, 0xC2, 0xA4, 0x0D, 0x01, 0xFD, 0x45, 0x10, 0x3A, 0x94, 0x41, 0xB5, 0x62, 0x13, 0x80, 0x2C, 0x00, 0x02, 0x00, 0x06, 0x14, 0x15, 0x0C, 0x15, 0x15, 0x00, 0x2A, + 0x00, 0x00, 0xE0, 0x4A, 0x01, 0x03, 0xB2, 0xBD, 0xD2, 0x04, 0xB1, 0x18, 0x00, 0x5D, 0x11, 0x40, 0xAB, 0xA0, 0xC8, 0xDF, 0x15, 0x82, 0xB1, 0xB9, 0xAB, 0x57, 0xF1, 0xE9, 0xC1, 0x03, 0x4A, 0x46, + 0x10, 0x3A, 0xA7, 0x43, 0xB5, 0x62, 0x13, 0x80, 0x2C, 0x00, 0x02, 0x00, 0x06, 0x15, 0x15, 0x0C, 0x15, 0x15, 0x00, 0x2A, 0x00, 0x00, 0xE0, 0x4A, 0x01, 0x03, 0xB3, 0xBD, 0xD2, 0x04, 0x28, 0x00, + 0x02, 0x5E, 0x5E, 0x40, 0x2B, 0xA9, 0x27, 0xA6, 0x73, 0x88, 0xE6, 0x73, 0xAE, 0x57, 0xCC, 0x8C, 0xAD, 0x02, 0x36, 0x3F, 0x14, 0x3A, 0xAF, 0x7C, 0xB5, 0x62, 0x13, 0x80, 0x2C, 0x00, 0x02, 0x00, + 0x06, 0x16, 0x15, 0x0C, 0x15, 0x15, 0x00, 0x2A, 0x00, 0x00, 0xE0, 0x4A, 0x01, 0x03, 0xB4, 0xBD, 0xD2, 0x04, 0x85, 0xEC, 0x00, 0x91, 0xEE, 0x3F, 0x6B, 0xB7, 0x72, 0xF2, 0xD1, 0x84, 0x7D, 0xBE, + 0xB0, 0x57, 0xEE, 0x05, 0x09, 0x08, 0x71, 0x43, 0x18, 0x3A, 0x1D, 0x65, 0xB5, 0x62, 0x13, 0x80, 0x2C, 0x00, 0x02, 0x00, 0x06, 0x17, 0x15, 0x0C, 0x15, 0x15, 0x00, 0x2A, 0x00, 0x00, 0xE0, 0x4A, + 0x01, 0x03, 0xB5, 0xBD, 0xD2, 0x04, 0xB1, 0x72, 0x02, 0x0E, 0xDA, 0x3E, 0xEB, 0xB8, 0xE4, 0xB4, 0xAF, 0x80, 0x21, 0xAF, 0xB3, 0x57, 0x7C, 0x50, 0x49, 0x00, 0x24, 0x43, 0x18, 0x3A, 0x26, 0xE7, + 0xB5, 0x62, 0x13, 0x80, 0x2C, 0x00, 0x02, 0x00, 0x06, 0x18, 0x15, 0x0C, 0x15, 0x15, 0x00, 0x2A, 0x00, 0x00, 0xE0, 0x4A, 0x01, 0x03, 0xB6, 0xBD, 0xD2, 0x04, 0xE1, 0x9D, 0x00, 0xA2, 0x0C, 0x40, + 0xAB, 0xC0, 0xEA, 0x39, 0xDD, 0xFD, 0x09, 0xEC, 0xA1, 0x57, 0x92, 0xAE, 0x92, 0x03, 0x96, 0x2B, 0x08, 0x3A, 0x64, 0xD1, 0xB5, 0x62, 0x13, 0x80, 0x78, 0x00, 0x03, 0x00, 0x00, 0xFF, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0x4A, 0x01, 0x03, 0x19, 0x00, 0x00, 0x05, 0x03, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0xB0, 0x07, 0x00, 0x8A, 0x08, 0x89, 0x08, 0x12, 0x07, + 0x12, 0x00, 0xC1, 0x03, 0x00, 0x00, 0x19, 0x00, 0x00, 0x07, 0x00, 0x00, 0x80, 0x32, 0x00, 0x00, 0x80, 0xB2, 0x00, 0x00, 0x80, 0xB3, 0x00, 0x00, 0x00, 0x34, 0x00, 0x00, 0xEC, 0x47, 0x00, 0x00, + 0x60, 0xC8, 0x00, 0x00, 0x80, 0xC7, 0x00, 0x00, 0x50, 0x49, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xD1, 0x22, 0xB5, 0x62, 0x13, 0x80, 0x2C, 0x00, 0x03, 0x00, 0x06, 0xFF, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0x4A, 0x01, 0x03, 0x12, 0x00, 0x06, 0x07, 0x11, 0x00, 0x06, 0x15, 0x01, 0x00, 0x00, 0x00, 0x1E, 0x00, 0x00, 0x00, 0xBD, 0x3A, 0xBD, 0x02, 0x92, 0xC7, + 0x1E, 0x00, 0xBD, 0x3A, 0x00, 0x00, 0x83, 0xEB, 0xB5, 0x62, 0x13, 0x80, 0x58, 0x00, 0x05, 0x00, 0x00, 0x02, 0x15, 0x0C, 0x04, 0x14, 0x0F, 0x03, 0x06, 0x1E, 0xE0, 0x4A, 0x01, 0x03, 0x01, 0x5E, + 0xAD, 0x14, 0x0D, 0xD9, 0x01, 0x99, 0x8A, 0x08, 0x57, 0xFD, 0x00, 0x00, 0x00, 0xC4, 0xA0, 0xFF, 0xAF, 0x13, 0x31, 0x04, 0x00, 0xED, 0x07, 0xBB, 0xE2, 0xC1, 0xE2, 0xA6, 0xA2, 0x5F, 0x18, 0xC2, + 0x8E, 0x08, 0x42, 0xC9, 0xD5, 0x17, 0x7A, 0x3A, 0x00, 0x00, 0xAA, 0x6C, 0x07, 0x20, 0x00, 0x50, 0xF8, 0xAB, 0xA6, 0xD0, 0xC2, 0x7C, 0xE2, 0xDC, 0x80, 0x36, 0x92, 0x07, 0x9C, 0x36, 0xA1, 0x2E, + 0x78, 0x0C, 0x91, 0xCE, 0x7E, 0xE6, 0x3E, 0x63, 0xB5, 0x62, 0x13, 0x80, 0x58, 0x00, 0x05, 0x00, 0x00, 0x05, 0x15, 0x0C, 0x04, 0x14, 0x1E, 0x03, 0x06, 0x1E, 0xE0, 0x4A, 0x01, 0x03, 0x04, 0x5E, + 0xAD, 0x14, 0xBB, 0x54, 0x01, 0x85, 0x8A, 0x08, 0x58, 0xFD, 0x00, 0x00, 0x40, 0xC4, 0xA9, 0xFF, 0xAF, 0x13, 0xEE, 0xD1, 0x3F, 0xF4, 0xE8, 0xBA, 0x02, 0x42, 0xDB, 0xA6, 0xE2, 0x4F, 0x93, 0xCC, + 0x86, 0x08, 0x5E, 0xE0, 0xFB, 0x01, 0x7A, 0x3A, 0x00, 0x00, 0xAA, 0x6C, 0x08, 0x20, 0xB2, 0xC2, 0xA1, 0xA2, 0x5A, 0xF1, 0x5A, 0x67, 0xD2, 0x32, 0xCB, 0x87, 0x73, 0xEA, 0xA6, 0x22, 0x7E, 0xCE, + 0xB7, 0xF8, 0x59, 0xCB, 0x13, 0xCF, 0x90, 0x3D, 0xB5, 0x62, 0x13, 0x80, 0x58, 0x00, 0x05, 0x00, 0x00, 0x07, 0x15, 0x0C, 0x04, 0x14, 0x1E, 0x03, 0x06, 0x1E, 0xE0, 0x4A, 0x01, 0x03, 0x06, 0x5E, + 0xAD, 0x14, 0x1C, 0x84, 0x01, 0x3C, 0x8A, 0x08, 0x58, 0x01, 0x00, 0x00, 0x40, 0x44, 0x5B, 0x01, 0xA0, 0x13, 0xB1, 0x77, 0x00, 0xF4, 0xE8, 0xBA, 0x02, 0x02, 0xDC, 0xA6, 0x62, 0x0D, 0x9B, 0x10, + 0x8D, 0x08, 0xE8, 0xB3, 0x12, 0x04, 0x7A, 0x3A, 0x00, 0x00, 0xAA, 0x6C, 0x08, 0x20, 0xD3, 0xB5, 0xB7, 0x0A, 0x9E, 0x12, 0x42, 0xA2, 0x14, 0xB9, 0xAD, 0xE8, 0xD6, 0xBA, 0xA1, 0x04, 0xEB, 0x56, + 0xE5, 0x29, 0xD2, 0xF1, 0x3E, 0xD4, 0x51, 0xD3, 0xB5, 0x62, 0x13, 0x80, 0x58, 0x00, 0x05, 0x00, 0x00, 0x09, 0x15, 0x0C, 0x04, 0x14, 0x1E, 0x03, 0x06, 0x1E, 0xE0, 0x4A, 0x01, 0x03, 0x08, 0x5E, + 0xAD, 0x14, 0xAE, 0x75, 0x01, 0xEB, 0x8A, 0x08, 0x58, 0x7D, 0x00, 0x00, 0x40, 0x44, 0x27, 0x00, 0xA0, 0x13, 0x2A, 0x09, 0x80, 0x01, 0xE8, 0xBA, 0x02, 0xC2, 0xDC, 0xA6, 0x42, 0x7D, 0x43, 0x25, + 0x97, 0x08, 0x0E, 0x55, 0xE8, 0x19, 0x7A, 0x3A, 0x00, 0x00, 0xAA, 0x6C, 0x08, 0x20, 0x34, 0x43, 0x60, 0xE2, 0x59, 0x42, 0xAE, 0xE1, 0xE5, 0x34, 0x86, 0x80, 0xA7, 0xDF, 0xA3, 0xE9, 0x4E, 0xB1, + 0xF7, 0x28, 0xB4, 0xBC, 0x8B, 0xD9, 0x5E, 0x5F, 0xB5, 0x62, 0x13, 0x80, 0x58, 0x00, 0x05, 0x00, 0x00, 0x0D, 0x15, 0x0C, 0x04, 0x14, 0x1E, 0x03, 0x06, 0x1E, 0xE0, 0x4A, 0x01, 0x03, 0x0C, 0x5E, + 0xAD, 0x14, 0x79, 0x8D, 0x01, 0x81, 0x8A, 0x08, 0x58, 0x01, 0x00, 0x00, 0x40, 0x44, 0x91, 0x01, 0xA0, 0x13, 0x16, 0xD5, 0x3F, 0xF4, 0xE8, 0xBA, 0x02, 0x42, 0xDC, 0xA6, 0x62, 0x60, 0x15, 0x2D, + 0x81, 0x08, 0x74, 0x9A, 0x0E, 0x09, 0x7A, 0x3A, 0x00, 0x00, 0xAA, 0x6C, 0x08, 0x20, 0x43, 0x1D, 0xEA, 0x22, 0xB7, 0x2E, 0xD8, 0x3E, 0x59, 0x3A, 0x55, 0x75, 0x09, 0xE7, 0x0A, 0x39, 0xC6, 0x02, + 0x6D, 0xFD, 0xE5, 0xF4, 0x5E, 0xEB, 0x85, 0x41, 0xB5, 0x62, 0x13, 0x80, 0x58, 0x00, 0x05, 0x00, 0x00, 0x10, 0x15, 0x0C, 0x04, 0x14, 0x1E, 0x03, 0x06, 0x1E, 0xE0, 0x4A, 0x01, 0x03, 0x0F, 0x5E, + 0xAD, 0x14, 0xB3, 0xBB, 0x01, 0x4E, 0x8A, 0x08, 0x58, 0xFD, 0x00, 0x00, 0x40, 0xC4, 0x49, 0xFE, 0xAF, 0x13, 0x9A, 0xA4, 0x3F, 0xF5, 0xE8, 0xBA, 0x02, 0x02, 0xDC, 0xA6, 0xA2, 0x33, 0xC9, 0x6D, + 0x90, 0x08, 0x5A, 0x9A, 0xE3, 0x00, 0x7A, 0x3A, 0x00, 0x00, 0xAA, 0x6C, 0x08, 0x20, 0x0D, 0xDD, 0x6D, 0xCD, 0xC2, 0x40, 0x3A, 0x97, 0x5C, 0x01, 0x02, 0x3D, 0x12, 0x48, 0x55, 0xDE, 0xBA, 0x35, + 0x8E, 0xFB, 0xE1, 0xF2, 0x92, 0xCE, 0x5C, 0xFB, 0xB5, 0x62, 0x13, 0x80, 0x58, 0x00, 0x05, 0x00, 0x00, 0x14, 0x15, 0x0C, 0x04, 0x14, 0x1E, 0x03, 0x06, 0x1E, 0xE0, 0x4A, 0x01, 0x03, 0x13, 0x5E, + 0xAD, 0x14, 0x31, 0xC2, 0x01, 0x0F, 0x8A, 0x08, 0x58, 0x81, 0x00, 0x00, 0x40, 0x44, 0xF0, 0xFF, 0xAF, 0x13, 0x98, 0x87, 0x3F, 0xF7, 0xE8, 0xBA, 0x02, 0xC2, 0xDC, 0xA6, 0x42, 0x50, 0xFF, 0x9B, + 0x8B, 0x08, 0x16, 0xF0, 0x21, 0x1C, 0x7A, 0x3A, 0x00, 0x00, 0xAA, 0x6C, 0x08, 0x20, 0x93, 0x50, 0xE6, 0x06, 0xCF, 0xB8, 0x8C, 0x20, 0xB6, 0x0C, 0x26, 0x4A, 0xC1, 0xC6, 0x7E, 0x22, 0xAC, 0xC6, + 0x13, 0x14, 0x27, 0xFF, 0x18, 0xD2, 0xBA, 0xBA, 0xB5, 0x62, 0x13, 0x80, 0x58, 0x00, 0x05, 0x00, 0x00, 0x1D, 0x15, 0x0C, 0x04, 0x14, 0x1E, 0x03, 0x06, 0x1E, 0xE0, 0x4A, 0x01, 0x03, 0x1C, 0x5E, + 0xAD, 0x14, 0x13, 0x8D, 0x01, 0xBC, 0x8A, 0x08, 0x58, 0xFD, 0x00, 0x00, 0x40, 0xC4, 0x8F, 0xFE, 0xAF, 0x13, 0x52, 0xDC, 0x00, 0xF5, 0xE8, 0xBA, 0x02, 0xC2, 0xDC, 0xA6, 0xC2, 0x76, 0x7B, 0x55, + 0x91, 0x08, 0x2A, 0xA2, 0xE2, 0x18, 0x7A, 0x3A, 0x00, 0x00, 0xAA, 0x6C, 0x08, 0x20, 0x0F, 0xB0, 0x0B, 0x57, 0x2E, 0x66, 0x57, 0xE9, 0x32, 0x5E, 0x4E, 0x14, 0x13, 0x5E, 0xFF, 0x22, 0xFA, 0x2A, + 0x77, 0xDB, 0x2C, 0xC6, 0xC6, 0xDE, 0x74, 0x97, 0xB5, 0x62, 0x13, 0x80, 0x58, 0x00, 0x05, 0x00, 0x00, 0x1E, 0x15, 0x0C, 0x04, 0x14, 0x1E, 0x03, 0x06, 0x1E, 0xE0, 0x4A, 0x01, 0x03, 0x1D, 0x5E, + 0xAD, 0x14, 0x8D, 0x68, 0x01, 0xBB, 0x8A, 0x08, 0x58, 0xFD, 0x00, 0x00, 0x40, 0x44, 0x28, 0xFF, 0xAF, 0x13, 0x5F, 0x5B, 0x00, 0x04, 0xE8, 0xBA, 0x02, 0x42, 0xDC, 0xA6, 0x22, 0x7D, 0x16, 0x76, + 0x89, 0x08, 0xDE, 0x80, 0xDF, 0x08, 0x7A, 0x3A, 0x00, 0x00, 0xAA, 0x6C, 0x08, 0x20, 0xE3, 0x37, 0xB1, 0x7F, 0xE8, 0xDA, 0x6C, 0x66, 0x21, 0x07, 0x1F, 0x55, 0x27, 0x27, 0x42, 0x1B, 0x68, 0x58, + 0x2E, 0x2E, 0x19, 0xA5, 0xC5, 0xE3, 0xBB, 0x38 +}; diff --git a/examples/AssistNow/AssistNow_Autonomous/Example3_AssistNowAutonomous_DatabaseWrite/secrets.h b/examples/AssistNow/AssistNow_Autonomous/Example3_AssistNowAutonomous_DatabaseWrite/secrets.h new file mode 100644 index 0000000..4eb7a9c --- /dev/null +++ b/examples/AssistNow/AssistNow_Autonomous/Example3_AssistNowAutonomous_DatabaseWrite/secrets.h @@ -0,0 +1,6 @@ +//Your WiFi credentials +const char ssid[] = "TRex"; +const char password[] = "hasBigTeeth"; + +//Your AssistNow token +const char myAssistNowToken[] = "58XXXXXXXXXXXXXXXXXXYQ"; diff --git a/examples/AssistNow/AssistNow_Offline/Example1_AssistNowOffline/Example1_AssistNowOffline.ino b/examples/AssistNow/AssistNow_Offline/Example1_AssistNowOffline/Example1_AssistNowOffline.ino new file mode 100644 index 0000000..a0836e4 --- /dev/null +++ b/examples/AssistNow/AssistNow_Offline/Example1_AssistNowOffline/Example1_AssistNowOffline.ino @@ -0,0 +1,325 @@ +/* + Use ESP32 WiFi to get AssistNow Offline data from u-blox Thingstream + By: SparkFun Electronics / Paul Clark + Date: November 26th, 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 AssistNow Offline data from u-blox Thingstream over WiFi + and push it over I2C to a u-blox module. + + The module still needs to be given time assistance to achieve a fast fix. This example + uses network time to do that. If you don't have a WiFi connection, you may have to use + a separate RTC to provide the time. + + Note: AssistNow Offline is not supported by the ZED-F9P! "The ZED-F9P supports AssistNow Online only." + + You will need to have a token to be able to access Thingstream. See the AssistNow README for more details. + + Update secrets.h with your: + - WiFi credentials + - AssistNow token string + + Uncomment the "#define USE_MGA_ACKs" below to test the more robust method of using the + UBX_MGA_ACK_DATA0 acknowledgements to confirm that each MGA message has been accepted. + + Feel like supporting open source hardware? + Buy a board from SparkFun! + SparkFun Thing Plus - ESP32 WROOM: https://www.sparkfun.com/products/15663 + SparkFun GPS Breakout - ZOE-M8Q (Qwiic): https://www.sparkfun.com/products/15193 + + 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 +*/ + +//#define USE_MGA_ACKs // Uncomment this line to use the UBX_MGA_ACK_DATA0 acknowledgements + +#include +#include +#include "secrets.h" + +const char assistNowServer[] = "https://offline-live1.services.u-blox.com"; +//const char assistNowServer[] = "https://offline-live2.services.u-blox.com"; // Alternate server + +const char getQuery[] = "GetOfflineData.ashx?"; +const char tokenPrefix[] = "token="; +const char tokenSuffix[] = ";"; +const char getGNSS[] = "gnss=gps,glo;"; // GNSS can be: gps,qzss,glo,bds,gal +const char getFormat[] = "format=mga;"; // Data format. Leave set to mga for M8 onwards. Can be aid. +const char getPeriod[] = "period=1;"; // Optional. The number of weeks into the future that the data will be valid. Can be 1-5. Default = 4. +const char getMgaResolution[] = "resolution=1;"; // Optional. Data resolution: 1 = every day; 2 = every other day; 3 = every 3rd day. +//Note: always use resolution=1. findMGAANOForDate does not yet support finding the 'closest' date. It needs an exact match. + +//=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + +#include //http://librarymanager/All#SparkFun_u-blox_GNSS +SFE_UBLOX_GNSS myGNSS; + +//=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + +#include "time.h" + +const char* ntpServer = "pool.ntp.org"; // The Network Time Protocol Server + +//=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + +void setup() +{ + delay(1000); + + Serial.begin(115200); + Serial.println(F("AssistNow Example")); + + while (Serial.available()) Serial.read(); // Empty the serial buffer + Serial.println(F("Press any key to begin...")); + while (!Serial.available()); // Wait for a keypress + + //=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + // Start I2C. Connect to the GNSS. + + 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 + + //=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + // Connect to WiFi. + + Serial.print(F("Connecting to local WiFi")); + + WiFi.begin(ssid, password); + while (WiFi.status() != WL_CONNECTED) { + delay(500); + Serial.print(F(".")); + } + Serial.println(); + + Serial.println(F("WiFi connected!")); + + //=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + // Set the RTC using network time. (Code taken from the SimpleTime example.) + + // Request the time from the NTP server and use it to set the ESP32's RTC. + configTime(0, 0, ntpServer); // Set the GMT and daylight offsets to zero. We need UTC, not local time. + + struct tm timeinfo; + if(!getLocalTime(&timeinfo)) + { + Serial.println("Failed to obtain time"); + } + else + { + Serial.println(&timeinfo, "Time is: %A, %B %d %Y %H:%M:%S"); + } + + //=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + // Use HTTP GET to receive the AssistNow_Online data + + const int URL_BUFFER_SIZE = 256; + char theURL[URL_BUFFER_SIZE]; // This will contain the HTTP URL + int payloadSize = 0; // This will be updated with the length of the data we get from the server + String payload; // This will store the data we get from the server + + // Assemble the URL + // Note the slash after the first %s (assistNowServer) + snprintf(theURL, URL_BUFFER_SIZE, "%s/%s%s%s%s%s%s%s%s", + assistNowServer, + getQuery, + tokenPrefix, + myAssistNowToken, + tokenSuffix, + getGNSS, + getFormat, + getPeriod, + getMgaResolution + ); + + Serial.print(F("HTTP URL is: ")); + Serial.println(theURL); + + HTTPClient http; + + http.begin(theURL); + + int httpCode = http.GET(); // HTTP GET + + // httpCode will be negative on error + if(httpCode > 0) + { + // HTTP header has been sent and Server response header has been handled + Serial.printf("[HTTP] GET... code: %d\r\n", httpCode); + + // If the GET was successful, read the data + if(httpCode == HTTP_CODE_OK) // Check for code 200 + { + payloadSize = http.getSize(); + Serial.printf("Server returned %d bytes\r\n", payloadSize); + + payload = http.getString(); // Get the payload + + // Pretty-print the payload as HEX + /* + int i; + for(i = 0; i < payloadSize; i++) + { + if (payload[i] < 0x10) // Print leading zero + Serial.print("0"); + Serial.print(payload[i], HEX); + Serial.print(" "); + if ((i % 16) == 15) + Serial.println(); + } + if ((i % 16) != 15) + Serial.println(); + */ + } + } + else + { + Serial.printf("[HTTP] GET... failed, error: %s\r\n", http.errorToString(httpCode).c_str()); + } + + http.end(); + + //=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + // Find where the AssistNow data for today starts and ends + + size_t todayStart = 0; // Default to sending all the data + size_t tomorrowStart = (size_t)payloadSize; + + // Uncomment the next line to enable the 'major' debug messages on Serial so you can see what AssistNow data is being sent + //myGNSS.enableDebugging(Serial, true); + + if (payloadSize > 0) + { + if(getLocalTime(&timeinfo)) + { + // Find the start of today's data + todayStart = myGNSS.findMGAANOForDate(payload, (size_t)payloadSize, timeinfo.tm_year + 1900, timeinfo.tm_mon + 1, timeinfo.tm_mday); + if (todayStart < (size_t)payloadSize) + { + Serial.print(F("Found the data for today starting at location ")); + Serial.println(todayStart); + } + else + { + Serial.println("Could not find the data for today. This will not work well. The GNSS needs help to start up quickly."); + } + + // Find the start of tomorrow's data + tomorrowStart = myGNSS.findMGAANOForDate(payload, (size_t)payloadSize, timeinfo.tm_year + 1900, timeinfo.tm_mon + 1, timeinfo.tm_mday, 1); + if (tomorrowStart < (size_t)payloadSize) + { + Serial.print(F("Found the data for tomorrow starting at location ")); + Serial.println(tomorrowStart); + } + else + { + Serial.println("Could not find the data for tomorrow. (Today's data may be the last?)"); + } + } + else + { + Serial.println("Failed to obtain time. This will not work well. The GNSS needs accurate time to start up quickly."); + } + } + + //=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + // Push the RTC time to the module + + if(getLocalTime(&timeinfo)) // Get the local time again, just to make sure we are using the most accurate time + { + // setUTCTimeAssistance uses a default time accuracy of 2 seconds which should be OK here. + // Have a look at the library source code for more details. + myGNSS.setUTCTimeAssistance(timeinfo.tm_year + 1900, timeinfo.tm_mon + 1, timeinfo.tm_mday, + timeinfo.tm_hour, timeinfo.tm_min, timeinfo.tm_sec); + } + else + { + Serial.println("Failed to obtain time. This will not work well. The GNSS needs accurate time to start up quickly."); + } + + //=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + // Push the AssistNow data for today to the module - without the time + + if (payloadSize > 0) + { + +#ifndef USE_MGA_ACKs + + // ***** Don't use the UBX_MGA_ACK_DATA0 messages ***** + + // Push the AssistNow data for today. Don't use UBX_MGA_ACK_DATA0's. Use the default delay of 7ms between messages. + myGNSS.pushAssistNowData(todayStart, true, payload, tomorrowStart - todayStart); + +#else + + // ***** Use the UBX_MGA_ACK_DATA0 messages ***** + + // Tell the module to return UBX_MGA_ACK_DATA0 messages when we push the AssistNow data + myGNSS.setAckAiding(1); + + // Speed things up by setting setI2CpollingWait to 1ms + myGNSS.setI2CpollingWait(1); + + // Push the AssistNow data for today. + myGNSS.pushAssistNowData(todayStart, true, payload, tomorrowStart - todayStart, SFE_UBLOX_MGA_ASSIST_ACK_YES, 100); + + // Set setI2CpollingWait to 125ms to avoid pounding the I2C bus + myGNSS.setI2CpollingWait(125); + +#endif + + } + + //=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + // Disconnect the WiFi as it's no longer needed + + WiFi.disconnect(true); + WiFi.mode(WIFI_OFF); + Serial.println(F("WiFi disconnected")); +} + +//=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + +void loop() +{ + // Print the UBX-NAV-PVT data so we can see how quickly the fixType goes to 3D + + long latitude = myGNSS.getLatitude(); + Serial.print(F("Lat: ")); + Serial.print(latitude); + + long longitude = myGNSS.getLongitude(); + Serial.print(F(" Long: ")); + Serial.print(longitude); + Serial.print(F(" (degrees * 10^-7)")); + + long altitude = myGNSS.getAltitude(); + Serial.print(F(" Alt: ")); + Serial.print(altitude); + Serial.print(F(" (mm)")); + + byte SIV = myGNSS.getSIV(); + Serial.print(F(" SIV: ")); + Serial.print(SIV); + + byte fixType = myGNSS.getFixType(); + Serial.print(F(" Fix: ")); + if(fixType == 0) Serial.print(F("No fix")); + 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 == 4) Serial.print(F("GNSS + Dead reckoning")); + else if(fixType == 5) Serial.print(F("Time only")); + + Serial.println(); +} diff --git a/examples/AssistNow/AssistNow_Offline/Example1_AssistNowOffline/secrets.h b/examples/AssistNow/AssistNow_Offline/Example1_AssistNowOffline/secrets.h new file mode 100644 index 0000000..4eb7a9c --- /dev/null +++ b/examples/AssistNow/AssistNow_Offline/Example1_AssistNowOffline/secrets.h @@ -0,0 +1,6 @@ +//Your WiFi credentials +const char ssid[] = "TRex"; +const char password[] = "hasBigTeeth"; + +//Your AssistNow token +const char myAssistNowToken[] = "58XXXXXXXXXXXXXXXXXXYQ"; diff --git a/examples/AssistNow/AssistNow_Online/Example1_AssistNowOnline/Example1_AssistNowOnline.ino b/examples/AssistNow/AssistNow_Online/Example1_AssistNowOnline/Example1_AssistNowOnline.ino new file mode 100644 index 0000000..24a3c5d --- /dev/null +++ b/examples/AssistNow/AssistNow_Online/Example1_AssistNowOnline/Example1_AssistNowOnline.ino @@ -0,0 +1,236 @@ +/* + Use ESP32 WiFi to get AssistNow Online data from u-blox Thingstream + By: SparkFun Electronics / Paul Clark + Date: November 24th, 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 AssistNow Online data from u-blox Thingstream over WiFi + and push it over I2C to a u-blox module. + + You will need to have a token to be able to access Thingstream. See the AssistNow README for more details. + + Update secrets.h with your: + - WiFi credentials + - AssistNow token string + + Uncomment the "#define USE_MGA_ACKs" below to test the more robust method of using the + UBX_MGA_ACK_DATA0 acknowledgements to confirm that each MGA message has been accepted. + + Feel like supporting open source hardware? + Buy a board from SparkFun! + SparkFun Thing Plus - ESP32 WROOM: https://www.sparkfun.com/products/15663 + ZED-F9P RTK2: https://www.sparkfun.com/products/16481 + SparkFun GPS Breakout - ZOE-M8Q (Qwiic): https://www.sparkfun.com/products/15193 + + 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 +*/ + +//#define USE_MGA_ACKs // Uncomment this line to use the UBX_MGA_ACK_DATA0 acknowledgements + +#include +#include +#include "secrets.h" + +const char assistNowServer[] = "https://online-live1.services.u-blox.com"; +//const char assistNowServer[] = "https://online-live2.services.u-blox.com"; // Alternate server + +const char getQuery[] = "GetOnlineData.ashx?"; +const char tokenPrefix[] = "token="; +const char tokenSuffix[] = ";"; +const char getGNSS[] = "gnss=gps,glo;"; // GNSS can be: gps,qzss,glo,bds,gal +const char getDataType[] = "datatype=eph,alm,aux;"; // Data type can be: eph,alm,aux,pos + +//=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + +#include //http://librarymanager/All#SparkFun_u-blox_GNSS +SFE_UBLOX_GNSS myGNSS; + +//=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + +void setup() +{ + delay(1000); + + Serial.begin(115200); + Serial.println(F("AssistNow Example")); + + while (Serial.available()) Serial.read(); // Empty the serial buffer + Serial.println(F("Press any key to begin...")); + while (!Serial.available()); // Wait for a keypress + + //=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + // Start I2C. Connect to the GNSS. + + 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 + + //=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + // Connect to WiFi. + + Serial.print(F("Connecting to local WiFi")); + + WiFi.begin(ssid, password); + while (WiFi.status() != WL_CONNECTED) { + delay(500); + Serial.print(F(".")); + } + Serial.println(); + + Serial.println(F("WiFi connected!")); + + //=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + // Use HTTP GET to receive the AssistNow_Online data + + const int URL_BUFFER_SIZE = 256; + char theURL[URL_BUFFER_SIZE]; // This will contain the HTTP URL + int payloadSize = 0; // This will be updated with the length of the data we get from the server + String payload; // This will store the data we get from the server + + // Assemble the URL + // Note the slash after the first %s (assistNowServer) + snprintf(theURL, URL_BUFFER_SIZE, "%s/%s%s%s%s%s%s", + assistNowServer, + getQuery, + tokenPrefix, + myAssistNowToken, + tokenSuffix, + getGNSS, + getDataType); + + Serial.print(F("HTTP URL is: ")); + Serial.println(theURL); + + HTTPClient http; + + http.begin(theURL); + + int httpCode = http.GET(); // HTTP GET + + // httpCode will be negative on error + if(httpCode > 0) + { + // HTTP header has been sent and Server response header has been handled + Serial.printf("[HTTP] GET... code: %d\r\n", httpCode); + + // If the GET was successful, read the data + if(httpCode == HTTP_CODE_OK) // Check for code 200 + { + payloadSize = http.getSize(); + Serial.printf("Server returned %d bytes\r\n", payloadSize); + + payload = http.getString(); // Get the payload + + // Pretty-print the payload as HEX + /* + int i; + for(i = 0; i < payloadSize; i++) + { + if (payload[i] < 0x10) // Print leading zero + Serial.print("0"); + Serial.print(payload[i], HEX); + Serial.print(" "); + if ((i % 16) == 15) + Serial.println(); + } + if ((i % 16) != 15) + Serial.println(); + */ + } + } + else + { + Serial.printf("[HTTP] GET... failed, error: %s\r\n", http.errorToString(httpCode).c_str()); + } + + http.end(); + + //=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + // Push the AssistNow data to the module + + if (payloadSize > 0) + { + // Uncomment the next line to enable the 'major' debug messages on Serial so you can see what AssistNow data is being sent + //myGNSS.enableDebugging(Serial, true); + +#ifndef USE_MGA_ACKs + + // ***** Don't use the UBX_MGA_ACK_DATA0 messages ***** + + // Push all the AssistNow data. Don't use UBX_MGA_ACK_DATA0's. Use the default delay of 7ms between messages. + myGNSS.pushAssistNowData(payload, (size_t)payloadSize); + +#else + + // ***** Use the UBX_MGA_ACK_DATA0 messages ***** + + // Tell the module to return UBX_MGA_ACK_DATA0 messages when we push the AssistNow data + myGNSS.setAckAiding(1); + + // Speed things up by setting setI2CpollingWait to 1ms + myGNSS.setI2CpollingWait(1); + + // Push all the AssistNow data. + // We have called setAckAiding(1) to instruct the module to return MGA-ACK messages. + // So, we could set the pushAssistNowData mgaAck parameter to SFE_UBLOX_MGA_ASSIST_ACK_YES. + // But, just for giggles, let's use SFE_UBLOX_MGA_ASSIST_ACK_ENQUIRE just to confirm that the + // MGA-ACK messages are actually enabled. + // Wait for up to 100ms for each ACK to arrive! 100ms is a bit excessive... 7ms is nearer the mark. + myGNSS.pushAssistNowData(payload, (size_t)payloadSize, SFE_UBLOX_MGA_ASSIST_ACK_ENQUIRE, 100); + + // Set setI2CpollingWait to 125ms to avoid pounding the I2C bus + myGNSS.setI2CpollingWait(125); + +#endif + + } + + Serial.println(F("Here we go!")); +} + +//=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + +void loop() +{ + // Print the UBX-NAV-PVT data so we can see how quickly the fixType goes to 3D + + long latitude = myGNSS.getLatitude(); + Serial.print(F("Lat: ")); + Serial.print(latitude); + + long longitude = myGNSS.getLongitude(); + Serial.print(F(" Long: ")); + Serial.print(longitude); + Serial.print(F(" (degrees * 10^-7)")); + + long altitude = myGNSS.getAltitude(); + Serial.print(F(" Alt: ")); + Serial.print(altitude); + Serial.print(F(" (mm)")); + + byte SIV = myGNSS.getSIV(); + Serial.print(F(" SIV: ")); + Serial.print(SIV); + + byte fixType = myGNSS.getFixType(); + Serial.print(F(" Fix: ")); + if(fixType == 0) Serial.print(F("No fix")); + 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 == 4) Serial.print(F("GNSS + Dead reckoning")); + else if(fixType == 5) Serial.print(F("Time only")); + + Serial.println(); +} diff --git a/examples/AssistNow/AssistNow_Online/Example1_AssistNowOnline/secrets.h b/examples/AssistNow/AssistNow_Online/Example1_AssistNowOnline/secrets.h new file mode 100644 index 0000000..4eb7a9c --- /dev/null +++ b/examples/AssistNow/AssistNow_Online/Example1_AssistNowOnline/secrets.h @@ -0,0 +1,6 @@ +//Your WiFi credentials +const char ssid[] = "TRex"; +const char password[] = "hasBigTeeth"; + +//Your AssistNow token +const char myAssistNowToken[] = "58XXXXXXXXXXXXXXXXXXYQ"; diff --git a/examples/AssistNow/AssistNow_Online/Example2_AssistNowOnline_TimeDelay/Example2_AssistNowOnline_TimeDelay.ino b/examples/AssistNow/AssistNow_Online/Example2_AssistNowOnline_TimeDelay/Example2_AssistNowOnline_TimeDelay.ino new file mode 100644 index 0000000..5ed4f3a --- /dev/null +++ b/examples/AssistNow/AssistNow_Online/Example2_AssistNowOnline_TimeDelay/Example2_AssistNowOnline_TimeDelay.ino @@ -0,0 +1,254 @@ +/* + Use ESP32 WiFi to get AssistNow Online data from u-blox Thingstream + By: SparkFun Electronics / Paul Clark + Date: November 24th, 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 AssistNow Online data from u-blox Thingstream over WiFi + and push it over I2C to a u-blox module. + + The AssistNow Online data is valid for 2-4 hours, so it can be re-used. + BUT you need to provide the time assistance separately. + This example shows how to do that. + The ESP32's RTC is set from network time. + The RTC time is pushed to the module using setUTCTimeAssistance, + followed by the AssistNow data (without time). + + You will need to have a token to be able to access Thingstream. See the AssistNow README for more details. + + Update secrets.h with your: + - WiFi credentials + - AssistNow token string + + Feel like supporting open source hardware? + Buy a board from SparkFun! + SparkFun Thing Plus - ESP32 WROOM: https://www.sparkfun.com/products/15663 + ZED-F9P RTK2: https://www.sparkfun.com/products/16481 + SparkFun GPS Breakout - ZOE-M8Q (Qwiic): https://www.sparkfun.com/products/15193 + + 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 +#include "secrets.h" + +const char assistNowServer[] = "https://online-live1.services.u-blox.com"; +//const char assistNowServer[] = "https://online-live2.services.u-blox.com"; // Alternate server + +const char getQuery[] = "GetOnlineData.ashx?"; +const char tokenPrefix[] = "token="; +const char tokenSuffix[] = ";"; +const char getGNSS[] = "gnss=gps,glo;"; // GNSS can be: gps,qzss,glo,bds,gal +const char getDataType[] = "datatype=eph,alm,aux;"; // Data type can be: eph,alm,aux,pos + +//=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + +#include //http://librarymanager/All#SparkFun_u-blox_GNSS +SFE_UBLOX_GNSS myGNSS; + +//=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + +#include "time.h" + +const char* ntpServer = "pool.ntp.org"; // The Network Time Protocol Server + +//=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + +void setup() +{ + delay(1000); + + Serial.begin(115200); + Serial.println(F("AssistNow Example")); + + while (Serial.available()) Serial.read(); // Empty the serial buffer + Serial.println(F("Press any key to begin...")); + while (!Serial.available()); // Wait for a keypress + + //=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + // Start I2C. Connect to the GNSS. + + 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 + + //=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + // Connect to WiFi. + + Serial.print(F("Connecting to local WiFi")); + + WiFi.begin(ssid, password); + while (WiFi.status() != WL_CONNECTED) + { + delay(500); + Serial.print(F(".")); + } + Serial.println(); + + Serial.println(F("WiFi connected!")); + + //=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + // Set the RTC using network time. (Code taken from the SimpleTime example.) + + // Request the time from the NTP server and use it to set the ESP32's RTC. + configTime(0, 0, ntpServer); // Set the GMT and daylight offsets to zero. We need UTC, not local time. + + struct tm timeinfo; + if(!getLocalTime(&timeinfo)) + { + Serial.println("Failed to obtain time"); + } + else + { + Serial.println(&timeinfo, "Time is: %A, %B %d %Y %H:%M:%S"); + } + + //=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + // Use HTTP GET to receive the AssistNow_Online data + + const int URL_BUFFER_SIZE = 256; + char theURL[URL_BUFFER_SIZE]; // This will contain the HTTP URL + int payloadSize = 0; // This will be updated with the length of the data we get from the server + String payload; // This will store the data we get from the server + + // Assemble the URL + // Note the slash after the first %s (assistNowServer) + snprintf(theURL, URL_BUFFER_SIZE, "%s/%s%s%s%s%s%s", + assistNowServer, + getQuery, + tokenPrefix, + myAssistNowToken, + tokenSuffix, + getGNSS, + getDataType); + + Serial.print(F("HTTP URL is: ")); + Serial.println(theURL); + + HTTPClient http; + + http.begin(theURL); + + int httpCode = http.GET(); // HTTP GET + + // httpCode will be negative on error + if(httpCode > 0) + { + // HTTP header has been sent and Server response header has been handled + Serial.printf("[HTTP] GET... code: %d\r\n", httpCode); + + // If the GET was successful, read the data + if(httpCode == HTTP_CODE_OK) // Check for code 200 + { + payloadSize = http.getSize(); + Serial.printf("Server returned %d bytes\r\n", payloadSize); + + payload = http.getString(); // Get the payload + + // Pretty-print the payload as HEX + /* + int i; + for(i = 0; i < payloadSize; i++) + { + if (payload[i] < 0x10) // Print leading zero + Serial.print("0"); + Serial.print(payload[i], HEX); + Serial.print(" "); + if ((i % 16) == 15) + Serial.println(); + } + if ((i % 16) != 15) + Serial.println(); + */ + } + } + else + { + Serial.printf("[HTTP] GET... failed, error: %s\r\n", http.errorToString(httpCode).c_str()); + } + + http.end(); + + //=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + // Push the RTC time to the module + + // Uncomment the next line to enable the 'major' debug messages on Serial so you can see what AssistNow data is being sent + //myGNSS.enableDebugging(Serial, true); + + if(getLocalTime(&timeinfo)) + { + // setUTCTimeAssistance uses a default time accuracy of 2 seconds which should be OK here. + // Have a look at the library source code for more details. + myGNSS.setUTCTimeAssistance(timeinfo.tm_year + 1900, timeinfo.tm_mon + 1, timeinfo.tm_mday, + timeinfo.tm_hour, timeinfo.tm_min, timeinfo.tm_sec); + } + else + { + Serial.println("Failed to obtain time. This will not work well. The GNSS needs accurate time to start up quickly."); + } + + //=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + // Push the AssistNow data to the module - WITHOUT THE TIME + + if (payloadSize > 0) + { + // Push the AssistNow data. Don't use UBX_MGA_ACK_DATA0's. Use the default delay of 7ms between messages. + // The 'true' parameter tells pushAssistNowData not to push any time data from the payload. + myGNSS.pushAssistNowData(true, payload, (size_t)payloadSize); + } + + //=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + // Disconnect the WiFi as it's no longer needed + + WiFi.disconnect(true); + WiFi.mode(WIFI_OFF); + Serial.println(F("WiFi disconnected")); +} + +//=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + +void loop() +{ + // Print the UBX-NAV-PVT data so we can see how quickly the fixType goes to 3D + + long latitude = myGNSS.getLatitude(); + Serial.print(F("Lat: ")); + Serial.print(latitude); + + long longitude = myGNSS.getLongitude(); + Serial.print(F(" Long: ")); + Serial.print(longitude); + Serial.print(F(" (degrees * 10^-7)")); + + long altitude = myGNSS.getAltitude(); + Serial.print(F(" Alt: ")); + Serial.print(altitude); + Serial.print(F(" (mm)")); + + byte SIV = myGNSS.getSIV(); + Serial.print(F(" SIV: ")); + Serial.print(SIV); + + byte fixType = myGNSS.getFixType(); + Serial.print(F(" Fix: ")); + if(fixType == 0) Serial.print(F("No fix")); + 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 == 4) Serial.print(F("GNSS + Dead reckoning")); + else if(fixType == 5) Serial.print(F("Time only")); + + Serial.println(); +} diff --git a/examples/AssistNow/AssistNow_Online/Example2_AssistNowOnline_TimeDelay/secrets.h b/examples/AssistNow/AssistNow_Online/Example2_AssistNowOnline_TimeDelay/secrets.h new file mode 100644 index 0000000..4eb7a9c --- /dev/null +++ b/examples/AssistNow/AssistNow_Online/Example2_AssistNowOnline_TimeDelay/secrets.h @@ -0,0 +1,6 @@ +//Your WiFi credentials +const char ssid[] = "TRex"; +const char password[] = "hasBigTeeth"; + +//Your AssistNow token +const char myAssistNowToken[] = "58XXXXXXXXXXXXXXXXXXYQ"; diff --git a/examples/AssistNow/AssistNow_Online/Example3_AssistNowOnline_PositionAssist/Example3_AssistNowOnline_PositionAssist.ino b/examples/AssistNow/AssistNow_Online/Example3_AssistNowOnline_PositionAssist/Example3_AssistNowOnline_PositionAssist.ino new file mode 100644 index 0000000..75ba417 --- /dev/null +++ b/examples/AssistNow/AssistNow_Online/Example3_AssistNowOnline_PositionAssist/Example3_AssistNowOnline_PositionAssist.ino @@ -0,0 +1,299 @@ +/* + Use ESP32 WiFi to get AssistNow Online data from u-blox Thingstream + By: SparkFun Electronics / Paul Clark + Date: November 24th, 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 AssistNow Online data from u-blox Thingstream over WiFi + and push it over I2C to a u-blox module. + + This example shows how to provide initial position assistance. Uncomment #define USE_SERVER_ASSISTANCE + below to include the position in the AssistNow data request, instead of using setPositionAssistanceLLH. + + You will need to have a token to be able to access Thingstream. See the AssistNow README for more details. + + Update secrets.h with your: + - WiFi credentials + - AssistNow token string + + Feel like supporting open source hardware? + Buy a board from SparkFun! + SparkFun Thing Plus - ESP32 WROOM: https://www.sparkfun.com/products/15663 + ZED-F9P RTK2: https://www.sparkfun.com/products/16481 + SparkFun GPS Breakout - ZOE-M8Q (Qwiic): https://www.sparkfun.com/products/15193 + + 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 +*/ + +//#define USE_SERVER_ASSISTANCE // Uncomment this line to include the position in the AssistNow data request + +#include +#include +#include "secrets.h" + +const char assistNowServer[] = "https://online-live1.services.u-blox.com"; +//const char assistNowServer[] = "https://online-live2.services.u-blox.com"; // Alternate server + +const char getQuery[] = "GetOnlineData.ashx?"; +const char tokenPrefix[] = "token="; +const char tokenSuffix[] = ";"; +const char getGNSS[] = "gnss=gps,glo;"; // GNSS can be: gps,qzss,glo,bds,gal +const char getDataType[] = "datatype=eph,alm,aux;"; // Data type can be: eph,alm,aux,pos + +#ifdef USE_SERVER_ASSISTANCE +const char useLatitude[] = "lat=55.0;"; // Use an approximate latitude of 55 degrees north. Replace this with your latitude. +const char useLongitude[] = "lon=-1.0;"; // Use an approximate longitude of 1 degree west. Replace this with your longitude. +const char useAlt[] = "alt=100;"; // Use an approximate latitude of 100m above WGS84. Replace this with your altitude. +const char usePosAcc[] = "pacc=100000;"; // Use a position accuracy of 100000m (100km) +#endif + +//=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + +#include //http://librarymanager/All#SparkFun_u-blox_GNSS +SFE_UBLOX_GNSS myGNSS; + +//=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + +#include "time.h" + +const char* ntpServer = "pool.ntp.org"; // The Network Time Protocol Server + +//=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + +void setup() +{ + delay(1000); + + Serial.begin(115200); + Serial.println(F("AssistNow Example")); + + while (Serial.available()) Serial.read(); // Empty the serial buffer + Serial.println(F("Press any key to begin...")); + while (!Serial.available()); // Wait for a keypress + + //=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + // Start I2C. Connect to the GNSS. + + 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 + + //=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + // Connect to WiFi. + + Serial.print(F("Connecting to local WiFi")); + + WiFi.begin(ssid, password); + while (WiFi.status() != WL_CONNECTED) + { + delay(500); + Serial.print(F(".")); + } + Serial.println(); + + Serial.println(F("WiFi connected!")); + + //=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + // Set the RTC using network time. (Code taken from the SimpleTime example.) + + // Request the time from the NTP server and use it to set the ESP32's RTC. + configTime(0, 0, ntpServer); // Set the GMT and daylight offsets to zero. We need UTC, not local time. + + struct tm timeinfo; + if(!getLocalTime(&timeinfo)) + { + Serial.println("Failed to obtain time"); + } + else + { + Serial.println(&timeinfo, "Time is: %A, %B %d %Y %H:%M:%S"); + } + + //=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + // Use HTTP GET to receive the AssistNow_Online data + + const int URL_BUFFER_SIZE = 256; + char theURL[URL_BUFFER_SIZE]; // This will contain the HTTP URL + int payloadSize = 0; // This will be updated with the length of the data we get from the server + String payload; // This will store the data we get from the server + + // Assemble the URL + // Note the slash after the first %s (assistNowServer) +#ifdef USE_SERVER_ASSISTANCE + snprintf(theURL, URL_BUFFER_SIZE, "%s/%s%s%s%s%s%s%s%s%s%s", + assistNowServer, + getQuery, + tokenPrefix, + myAssistNowToken, + tokenSuffix, + getGNSS, + getDataType, + useLatitude, + useLongitude, + useAlt, + usePosAcc); +#else + snprintf(theURL, URL_BUFFER_SIZE, "%s/%s%s%s%s%s%s", + assistNowServer, + getQuery, + tokenPrefix, + myAssistNowToken, + tokenSuffix, + getGNSS, + getDataType); +#endif + + Serial.print(F("HTTP URL is: ")); + Serial.println(theURL); + + HTTPClient http; + + http.begin(theURL); + + int httpCode = http.GET(); // HTTP GET + + // httpCode will be negative on error + if(httpCode > 0) + { + // HTTP header has been sent and Server response header has been handled + Serial.printf("[HTTP] GET... code: %d\r\n", httpCode); + + // If the GET was successful, read the data + if(httpCode == HTTP_CODE_OK) // Check for code 200 + { + payloadSize = http.getSize(); + Serial.printf("Server returned %d bytes\r\n", payloadSize); + + payload = http.getString(); // Get the payload + + // Pretty-print the payload as HEX + /* + int i; + for(i = 0; i < payloadSize; i++) + { + if (payload[i] < 0x10) // Print leading zero + Serial.print("0"); + Serial.print(payload[i], HEX); + Serial.print(" "); + if ((i % 16) == 15) + Serial.println(); + } + if ((i % 16) != 15) + Serial.println(); + */ + } + } + else + { + Serial.printf("[HTTP] GET... failed, error: %s\r\n", http.errorToString(httpCode).c_str()); + } + + http.end(); + + //=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + // Speed things up by setting setI2CpollingWait to 1ms + myGNSS.setI2CpollingWait(1); + + //=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + // Tell the module to return UBX_MGA_ACK_DATA0 messages when we push the AssistNow data + myGNSS.setAckAiding(1); + + //=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + // Push the RTC time to the module + + // Uncomment the next line to enable the 'major' debug messages on Serial so you can see what AssistNow data is being sent + //myGNSS.enableDebugging(Serial, true); + + if(getLocalTime(&timeinfo)) + { + // Provide time assistance. Use the UBX_MGA_ACK_DATA0 acknowledgements. Set tAccS to 2 seconds. + myGNSS.setUTCTimeAssistance(timeinfo.tm_year + 1900, timeinfo.tm_mon + 1, timeinfo.tm_mday, + timeinfo.tm_hour, timeinfo.tm_min, timeinfo.tm_sec, 0, 2, 0, 0, SFE_UBLOX_MGA_ASSIST_ACK_YES, 100); + } + else + { + Serial.println("Failed to obtain time. This will not work well. The GNSS needs accurate time to start up quickly."); + } + + //=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + // If desired - push initial position assistance to the module + +#ifndef USE_SERVER_ASSISTANCE + + // Use 55 degrees (*10^7) north, 1 degree (*10^7) west, 100m (10000cm) altitude, 100km (10000000cm) accuracy. Replace these with your position. + // The units for lat and lon are degrees * 1e-7 (WGS84) + // The units for alt (WGS84) and posAcc (stddev) are cm. + myGNSS.setPositionAssistanceLLH(550000000, -10000000, 10000, 10000000, SFE_UBLOX_MGA_ASSIST_ACK_YES, 100); + + // We could use setPositionAssistanceXYZ instead if needed. + +#endif + + //=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + // Push the AssistNow data to the module - WITHOUT THE TIME + + if (payloadSize > 0) + { + // Push the AssistNow data. Use the UBX_MGA_ACK_DATA0 acknowledgements. + // The 'true' parameter tells pushAssistNowData not to push any time data from the payload. + myGNSS.pushAssistNowData(true, payload, (size_t)payloadSize, SFE_UBLOX_MGA_ASSIST_ACK_YES, 100); + } + + //=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + // Disconnect the WiFi as it's no longer needed + + WiFi.disconnect(true); + WiFi.mode(WIFI_OFF); + Serial.println(F("WiFi disconnected")); + + //=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + // Avoid pounding the I2C bus by setting setI2CpollingWait to 125ms + myGNSS.setI2CpollingWait(125); +} + +//=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + +void loop() +{ + // Print the UBX-NAV-PVT data so we can see how quickly the fixType goes to 3D + + long latitude = myGNSS.getLatitude(); + Serial.print(F("Lat: ")); + Serial.print(latitude); + + long longitude = myGNSS.getLongitude(); + Serial.print(F(" Long: ")); + Serial.print(longitude); + Serial.print(F(" (degrees * 10^-7)")); + + long altitude = myGNSS.getAltitude(); + Serial.print(F(" Alt: ")); + Serial.print(altitude); + Serial.print(F(" (mm)")); + + byte SIV = myGNSS.getSIV(); + Serial.print(F(" SIV: ")); + Serial.print(SIV); + + byte fixType = myGNSS.getFixType(); + Serial.print(F(" Fix: ")); + if(fixType == 0) Serial.print(F("No fix")); + 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 == 4) Serial.print(F("GNSS + Dead reckoning")); + else if(fixType == 5) Serial.print(F("Time only")); + + Serial.println(); +} diff --git a/examples/AssistNow/AssistNow_Online/Example3_AssistNowOnline_PositionAssist/secrets.h b/examples/AssistNow/AssistNow_Online/Example3_AssistNowOnline_PositionAssist/secrets.h new file mode 100644 index 0000000..4eb7a9c --- /dev/null +++ b/examples/AssistNow/AssistNow_Online/Example3_AssistNowOnline_PositionAssist/secrets.h @@ -0,0 +1,6 @@ +//Your WiFi credentials +const char ssid[] = "TRex"; +const char password[] = "hasBigTeeth"; + +//Your AssistNow token +const char myAssistNowToken[] = "58XXXXXXXXXXXXXXXXXXYQ"; diff --git a/examples/AssistNow/README.md b/examples/AssistNow/README.md new file mode 100644 index 0000000..d103235 --- /dev/null +++ b/examples/AssistNow/README.md @@ -0,0 +1,222 @@ +# SparkFun u-blox Arduino GNSS Library - AssistNowTM + +v2.1.0 of the library adds support for u-blox [AssistNowTM Assisted GNSS (A-GNSS)](https://www.u-blox.com/en/product/assistnow) which can dramatically reduce the time-to-first-fix. + +To use AssistNow Online or AssistNow Offline, you will need a token to access the u-blox Thingstream server. See [below](#AssistNow-Service-Token) for details. + +## AssistNowTM Online + +With AssistNow Online, an Internet connected host downloads assistance data from the u-blox AssistNow Online service to the receiver at system start-up. AssistNow Online data is valid for 2 - 4 hours; beyond that fresh data must be downloaded. + +Please see the [AssistNow_Online](./AssistNow_Online) examples for more details. These examples were written for the ESP32, but will run on other platforms too. + +The new functions we've added to the library to support AssistNow Online are described [Support for AssistNow below](#Support-for-AssistNow). + +## AssistNowTM Offline + +With the AssistNow Offline service, users can download long-term orbit data over the Internet at their convenience. The orbit data can be stored in the memory of the application processor. The function requires no connectivity at system start-up, enabling a position fix within seconds, even when no network is available. AssistNow Offline offers augmentation for up to 35 days. + +Please see the [AssistNow_Offline](./AssistNow_Offline) examples for more details. These examples were written for the ESP32, but will run on other platforms too. + +**Note: AssistNow Offline is not supported by the ZED-F9P. "The ZED-F9P supports AssistNow Online only."** + +The new functions we've added to the library to support AssistNow Offline are described [Support for AssistNow](#Support-for-AssistNow) and [Additional Support for AssistNow Offline](#Additional-Support-for-AssistNow-Offline). + +## AssistNowTM Autonomous + +AssistNow Autonomous provides aiding information without the need for a host or external network connection. Based on previous broadcast satellite ephemeris data downloaded to and stored by the GNSS receiver, AssistNow Autonomous automatically generates accurate predictions of satellite orbital data (“AssistNow Autonomous data”) that is usable for future GNSS position fixes. + +The benefits of AssistNow Autonomous are: + +* Faster fix in situations where GNSS satellite signals are weak +* No connectivity required +* Compatible with AssistNow Online (can work stand-alone, or in tandem with AssistNow Online service) +* No integration effort; calculations are done in the background, transparent to the user + +AssistNow Autonomous offers augmentation for up to 6 days. + +Please see the [AssistNow_Autonomous](./AssistNow_Autonomous) examples for more details. + +**Note: AssistNow Autonomous does not work on the ZED-F9P. "The ZED-F9P supports AssistNow Online only."** + +The new functions we've added to the library to support AssistNow Autonomous are described [Support for AssistNow Autonomous below](#Support-for-AssistNow-Autonomous). + +## AssistNow Service Token + +To be able to use AssistNow Online or AssistNow Offline, you will need a token to access the u-blox Thingstream server. + +The following u-blox resources contain useful information: + +* [AssistNow - u-blox A-GNSS services](https://www.u-blox.com/en/product/assistnow) +* [AssistNow Product Summary](https://www.u-blox.com/sites/default/files/products/documents/AssistNow_ProductSummary_UBX-13003352.pdf) +* [AssistNow User Guide](https://www.u-blox.com/sites/default/files/products/documents/MultiGNSS-Assistance_UserGuide_%28UBX-13004360%29.pdf) +* [Thingstream Pricing](https://portal.thingstream.io/pricing) + +You can apply for a _free_ AssistNow Service Evaluation Token by completing the request form: + +* [AssistNow Service evaluation token request form](https://www.u-blox.com/en/assistnow-service-evaluation-token-request-form) + +The _free_ AssistNow Developer token entitles you to: + +* AssistNow Online Developer: 100K free location requests per month. Capped. +* AssistNow Offline Developer: 20K free location requests per month. Capped. +* CellLocate Developer: 5K free location requests per month. Capped. + +The free token will expire after 90 days, but you can continue to use it beyond that by registering it on [Thingstream](https://portal.thingstream.io/). + +## Initial Position Assistance + +You can further decrease the time-to-first-fix by providing the receiver's approximate position - if known. There are two ways to do this: + +* The position can be specified when requesting AssistNow Online data from the server: + * include the key name ```lat``` with the approximate user latitude in WGS 84 expressed in degrees and fractional degrees. Must be in range -90 to 90. Example: ```lat=47.2;``` + * include the key name ```lon``` with the approximate user longitude in WGS 84 expressed in degrees and fractional degrees. Must be in range -180 to 180. Example: ```lon=8.55;``` + * include the key name ```alt``` with the approximate user altitude above WGS 84 Ellipsoid in meters. If this value is not provided, the server assumes an altitude of 0 meters. Must be in range -1000 to 50000 + * include the key name ```pacc``` with the approximate accuracy of submitted position in meters. If this value is not provided, the server assumes an accuracy of 300 km. Must be in range 0 to 6000000 + * the position assistance data will then be automatically included in the AssistNow Online data +* Provide initial position assistance data by calling one of: + * bool setPositionAssistanceXYZ(int32_t ecefX, int32_t ecefY, int32_t ecefZ, uint32_t posAcc, sfe_ublox_mga_assist_ack_e mgaAck, uint16_t maxWait); + * The units for ```ecefX/Y/Z``` and ```posAcc``` (stddev) are cm + * bool setPositionAssistanceLLH(int32_t lat, int32_t lon, int32_t alt, uint32_t posAcc, sfe_ublox_mga_assist_ack_e mgaAck, uint16_t maxWait); + * The units for ```lat``` and ```lon``` are degrees * 1e-7 (WGS84). The units for ```alt``` (WGS84) and ```posAcc``` (stddev) are cm (not m) + +## Support for AssistNow + +```pushAssistNowData``` allows AssistNow Online, Offline or Autonomous data to be pushed to the module. As the ESP32 HTTP GET function returns a ```String```, we've included overloaded functions which allow you to pass the data as a ```String``` or as ```const uint8_t *```. + +The String-based function declarations are: + +* size_t pushAssistNowData(const String &dataBytes, size_t numDataBytes, sfe_ublox_mga_assist_ack_e mgaAck, uint16_t maxWait); +* size_t pushAssistNowData(bool skipTime, const String &dataBytes, size_t numDataBytes, sfe_ublox_mga_assist_ack_e mgaAck, uint16_t maxWait); +* size_t pushAssistNowData(size_t offset, bool skipTime, const String &dataBytes, size_t numDataBytes, sfe_ublox_mga_assist_ack_e mgaAck, uint16_t maxWait); + +The const uint8_t * function declarations are: + +* size_t pushAssistNowData(const uint8_t *dataBytes, size_t numDataBytes, sfe_ublox_mga_assist_ack_e mgaAck, uint16_t maxWait); +* size_t pushAssistNowData(bool skipTime, const uint8_t *dataBytes, size_t numDataBytes, sfe_ublox_mga_assist_ack_e mgaAck, uint16_t maxWait); +* size_t pushAssistNowData(size_t offset, bool skipTime, const uint8_t *dataBytes, size_t numDataBytes, sfe_ublox_mga_assist_ack_e mgaAck, uint16_t maxWait); + +```dataBytes``` is a pointer to the AssistNow data. +
+```numDataBytes``` is the length of the AssistNow data. +
+ +```pushAssistNowData``` pushes individual packets of data to the u-blox module. Sending all of the data contiguously would overload the module, so ```pushAssistNowData``` can either: + +* insert a small delay between each packet (the default is 7ms) +* or use the ```UBX-MGA-ACK-DATA0``` acknowledgement message to acknowledge each packet + +```mgaAck``` controls which method is used. + +* if ```mgaAck``` is ```SFE_UBLOX_MGA_ASSIST_ACK_NO``` (**default**), a delay of ```maxWait``` milliseconds is inserted between each packet. ```maxWait``` defaults to 7ms. +* if ```mgaAck``` is ```SFE_UBLOX_MGA_ASSIST_ACK_YES```, acknowledgement messages will be expected with a _timeout_ of ```maxWait``` milliseconds. The default timeout is again 7ms, but you can change this if required by passing a different value. +* if ```mgaAck``` is ```SFE_UBLOX_MGA_ASSIST_ACK_ENQUIRE```, the code will poll the module to enquire if the acknowledgement messages are enabled. If they are, they will be used. If not, a delay is used. + +```setAckAiding``` enables or disables the acknowledgement messages. By default they are disabled. ```setAckAiding(1)``` will enable them. ```setAckAiding(0)``` will disable them again. + +* bool setAckAiding(uint8_t ackAiding, uint16_t maxWait); + +```getAckAiding``` returns 1 if the acknowledgement messages are enabled, 0 if they are disabled. 255 indicates an error or timeout. + +* uint8_t getAckAiding(uint16_t maxWait); + +```pushAssistNowData``` returns the number of _bytes_ pushed (not the number of _packets_). The return value should be equal to ```numDataBytes``` if all data was valid and pushed successfully. + +AssistNow Online data is valid for 2-4 hours. 'Stale' data can be re-used but: + +* ```pushAssistNowData``` needs to be told to skip the time information contained in the AssistNow data +* the user needs to provide the module with UTC time separately + +The ```skipTime``` parameter tells ```pushAssistNowData``` to skip any time information in the data. ```skipTime``` is bool. Set it to ```true``` to skip the time information. +
+ +UTC time can be pushed to the module first by calling ```setUTCTimeAssistance```: + +* bool setUTCTimeAssistance(uint16_t year, uint8_t month, uint8_t day, uint8_t hour, uint8_t minute, uint8_t second, uint32_t nanos, uint16_t tAccS, uint32_t tAccNs, uint8_t source, sfe_ublox_mga_assist_ack_e mgaAck, uint16_t maxWait); + +Only the ```year```, ```month```, ```day```, ```hour```, ```minute``` and ```second``` parameters are mandatory. The others default to sensible values. Again ```mgaAck``` and ```maxWait``` control if a delay is used when configuring the time, or if an acknowledgement message will be expected. +
+ +```nanos``` (nanoseconds), ```tAccS``` (time accuracy estimate (seconds)), ```tAccNs``` (time accuracy estimate (nanoseconds)) and ```source``` (if a clock signal will be provided on EXT_INT) are optional, but are available for advanced users. +
+ +```year``` numbering starts at 0; 2021 is 2021, not 121 (years since 1900). ```month``` and ```day``` numbering starts at 1, not 0. +
+ +Call ```setUTCTimeAssistance``` _before_ ```pushAssistNowData```. + +## Additional Support for AssistNow Offline + +AssistNow Offline data downloaded from the u-blox server can contain 1-5 weeks of data. However, only the data for _today_ should be pushed the module. Sending data for past or future days will confuse the module. +```findMGAANOForDate``` can be used to find the location of the start of the UBX-MGA-ANO data for the specified date within the offline data. That location can then be passed to ```pushAssistNowData``` using the ```offset``` parameter. + +* size_t findMGAANOForDate(const uint8_t *dataBytes, size_t numDataBytes, uint16_t year, uint8_t month, uint8_t day, uint8_t daysIntoFuture); + +The sequence of events is: + +* call ```findMGAANOForDate``` passing the ```year```, ```month``` and ```day``` for today. ```findMGAANOForDate``` will return the location / offset of the data for today within the offline data. +* call ```findMGAANOForDate``` again passing the ```year```, ```month``` and ```day``` for _today_ but also set ```daysIntoFuture``` to 1. ```findMGAANOForDate``` will then return the location / offset of the data for _tomorrow_ (one day into the future). +* call ```pushAssistNowData``` setting: + * ```offset``` to the location (offset) of today's data within the offline data + * ```skipTime``` to ```true``` + * ```numDataBytes``` to ((tomorrow's location) - (today's location)). Only the offline data for today will be pushed. + +```findMGAANOForDate``` will return a value of numDataBytes if the data for the chosen day cannot be found. +
+ +Again, call ```setUTCTimeAssistance``` _before_ ```pushAssistNowData```. + +## Support for AssistNow Autonomous + +AssistNow Autonomous is disabled by default. You can enable it by calling ```setAopCfg``` and check if it is enabled by calling ```getAopCfg```: + +* uint8_t getAopCfg(uint16_t maxWait); +* bool SFE_UBLOX_GNSS::setAopCfg(uint8_t aopCfg, uint16_t aopOrbMaxErr, uint16_t maxWait) + +```getAopCfg``` will return 1 if AssistNow Autonomous is enabled, 0 if disabled. 255 indicates an error or timeout. + +```setAopCfg``` has two parameters: + +* set ```aopCfg``` to 1 to enable AssistNow Autonomous, or 0 to disable it +* ```aopOrbMaxErr``` is used to set the 'lifetime' of the AssistNow data. It is recommended to set aopOrbMaxErr to 0 (the default value). This instructs the module to use the firmware default value that corresponds to a default orbit data validity of approximately three days (for GPS satellites observed once) and up to six days (for GPS and GLONASS satellites observed multiple times over a period of at least half a day). + +Once AssistNow Autonomous is enabled, you can monitor its status via the ```status``` field in the UBX-NAV-AOPSTATUS message. You can read the ```status``` by calling the helper function ```getAOPSTATUSstatus```. It will return zero when the AssistNow Autonomous data collection is idle. Non-zero values indicate that data collection is in progress. Only power-off the receiver when the subsystem is idle (that is, when the status shows a steady zero). + +* uint8_t getAOPSTATUSstatus(uint16_t maxWait); +* uint8_t getAOPSTATUSuseAOP(uint16_t maxWait); + +We have included full 'auto' support for UBX-NAV-AOPSTATUS, so you can have the message delivered periodically, add a callback for it, and/or log it to the file buffer: + +* bool getAOPSTATUS(uint16_t maxWait); +* bool setAutoAOPSTATUS(bool enabled, uint16_t maxWait); +* bool setAutoAOPSTATUS(bool enabled, bool implicitUpdate, uint16_t maxWait); +* bool setAutoAOPSTATUSrate(uint8_t rate, bool implicitUpdate, uint16_t maxWait); +* bool setAutoAOPSTATUScallback(void (*callbackPointer)(UBX_NAV_AOPSTATUS_data_t), uint16_t maxWait); +* bool assumeAutoAOPSTATUS(bool enabled, bool implicitUpdate); +* void flushAOPSTATUS(); +* void logAOPSTATUS(bool enabled); + +You can also monitor the AssistNow Autonomous satellite information via the UBX-NAV-SAT message. Again, we have included full 'auto' support for UBX-NAV-SAT. UBX-NAV-SAT contains useful information for each individual satellite which the module has acquired: carrier to noise ratio (signal strength); elevation; azimuth; pseudorange residual; quality indication, health; ephemeris available; almanac available; **AssistNow Offline data availability**; and more. The data can be analyzed using a callback. Please see the AssistNowAutonomous examples for more details. + +* bool getNAVSAT(uint16_t maxWait); +* bool setAutoNAVSAT(bool enabled, uint16_t maxWait); +* bool setAutoNAVSAT(bool enabled, bool implicitUpdate, uint16_t maxWait); +* bool setAutoNAVSATrate(uint8_t rate, bool implicitUpdate = true, uint16_t maxWait); +* bool setAutoNAVSATcallback(void (*callbackPointer)(UBX_NAV_NAVSAT_data_t), uint16_t maxWait); +* bool assumeAutoNAVSAT(bool enabled, bool implicitUpdate); +* void flushNAVSAT(); +* void logNAVSAT(bool enabled); + +The AssistNow Autonomous data is stored in the module's RAM memory. If that RAM is Battery-Backed - all SparkFun GNSS boards include battery back-up - then the data will be available after the module is powered down and powered back up again. However, you can also read (poll) the navigation database and store the contents in processor memory. ```readNavigationDatabase``` allows you to do that: + +* size_t readNavigationDatabase(uint8_t *dataBytes, size_t maxNumDataBytes, uint16_t maxWait); + +Data is written to ```dataBytes```. Set ```maxNumDataBytes``` to the (maximum) size of dataBytes. If the database exceeds maxNumDataBytes, the excess bytes will be lost. + +```readNavigationDatabase``` returns the number of database bytes written to ```dataBytes```. The return value will be equal to ```maxNumDataBytes``` if excess data was received. + +```readNavigationDatabase``` will timeout after ```maxWait``` milliseconds - in case the final UBX-MGA-ACK was missed. + +You can then write the database back into the module using ```pushAssistNowData```. Don't forget to call ```setUTCTimeAssistance``` _before_ ```pushAssistNowData```. + +Note: UBX-MGA-DBD messages are only intended to be sent back to the same receiver that generated them. They are firmware-specific. diff --git a/examples/Callbacks/CallbackExample7_NAV_SAT/CallbackExample7_NAV_SAT.ino b/examples/Callbacks/CallbackExample7_NAV_SAT/CallbackExample7_NAV_SAT.ino new file mode 100644 index 0000000..2612e47 --- /dev/null +++ b/examples/Callbacks/CallbackExample7_NAV_SAT/CallbackExample7_NAV_SAT.ino @@ -0,0 +1,121 @@ +/* + Configuring the GNSS to automatically send NAV SAT reports over I2C and display them using a callback + By: Paul Clark + SparkFun Electronics + Date: December 1st, 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 configure the u-blox GNSS to send NAV SAT reports automatically + and access the data via a callback. No more polling! + + Feel like supporting open source hardware? + Buy a board from SparkFun! + ZED-F9P RTK2: https://www.sparkfun.com/products/15136 + + Hardware Connections: + Plug a Qwiic cable into the GPS and a BlackBoard + 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 //Needed for I2C to GPS + +#include //http://librarymanager/All#SparkFun_u-blox_GNSS +SFE_UBLOX_GNSS myGNSS; + +// Callback: newNAVSAT will be called when new NAV SAT data arrives +// See u-blox_structs.h for the full definition of UBX_NAV_SAT_data_t +// _____ You can use any name you like for the callback. Use the same name when you call setAutoNAVSATcallback +// / _____ This _must_ be UBX_NAV_SAT_data_t +// | / _____ You can use any name you like for the struct +// | | / +// | | | +void newNAVSAT(UBX_NAV_SAT_data_t ubxDataStruct) +{ + Serial.println(); + + Serial.print(F("New NAV SAT data received. It contains data for ")); + Serial.print(ubxDataStruct.header.numSvs); + if (ubxDataStruct.header.numSvs == 1) + Serial.println(F(" SV.")); + else + Serial.println(F(" SVs.")); + + // Just for giggles, print the signal strength for each SV as a barchart + for (uint16_t block = 0; block < ubxDataStruct.header.numSvs; block++) // For each SV + { + switch (ubxDataStruct.blocks[block].gnssId) // Print the GNSS ID + { + case 0: + Serial.print(F("GPS ")); + break; + case 1: + Serial.print(F("SBAS ")); + break; + case 2: + Serial.print(F("Galileo ")); + break; + case 3: + Serial.print(F("BeiDou ")); + break; + case 4: + Serial.print(F("IMES ")); + break; + case 5: + Serial.print(F("QZSS ")); + break; + case 6: + Serial.print(F("GLONASS ")); + break; + default: + Serial.print(F("UNKNOWN ")); + break; + } + + Serial.print(ubxDataStruct.blocks[block].svId); // Print the SV ID + + if (ubxDataStruct.blocks[block].svId < 10) Serial.print(F(" ")); + else if (ubxDataStruct.blocks[block].svId < 100) Serial.print(F(" ")); + else Serial.print(F(" ")); + + // Print the signal strength as a bar chart + for (uint8_t cno = 0; cno < ubxDataStruct.blocks[block].cno; cno++) + Serial.print(F("=")); + + Serial.println(); + } +} + +void setup() +{ + Serial.begin(115200); + while (!Serial); //Wait for user to open terminal + Serial.println("SparkFun u-blox Example"); + + Wire.begin(); + + //myGNSS.enableDebugging(); // Uncomment this line to enable helpful debug messages on Serial + + if (myGNSS.begin() == false) //Connect to the u-blox module using Wire port + { + Serial.println(F("u-blox GNSS not detected at default I2C address. Please check wiring. Freezing.")); + while (1); + } + + myGNSS.setI2COutput(COM_TYPE_UBX); //Set the I2C port to output UBX only (turn off NMEA noise) + myGNSS.saveConfigSelective(VAL_CFG_SUBSEC_IOPORT); //Save (only) the communications port settings to flash and BBR + + myGNSS.setNavigationFrequency(1); //Produce one solution per second + + myGNSS.setAutoNAVSATcallback(&newNAVSAT); // Enable automatic NAV SAT messages with callback to newNAVSAT +} + +void loop() +{ + myGNSS.checkUblox(); // Check for the arrival of new data and process it. + myGNSS.checkCallbacks(); // Check if any callbacks are waiting to be processed. + + Serial.print("."); + delay(50); +} diff --git a/keywords.txt b/keywords.txt index d1268a2..60f06f1 100644 --- a/keywords.txt +++ b/keywords.txt @@ -90,6 +90,13 @@ checkCallbacks KEYWORD2 pushRawData KEYWORD2 +pushAssistNowData KEYWORD2 +setUTCTimeAssistance KEYWORD2 +setPositionAssistanceXYZ KEYWORD2 +setPositionAssistanceLLH KEYWORD2 +findMGAANOForDate KEYWORD2 +readNavigationDatabase KEYWORD2 + setFileBufferSize KEYWORD2 getFileBufferSize KEYWORD2 extractFileBufferData KEYWORD2 @@ -153,6 +160,21 @@ getDynamicModel KEYWORD2 resetOdometer KEYWORD2 enableGNSS KEYWORD2 +isGNSSenabled KEYWORD2 + +resetIMUalignment KEYWORD2 + +getESFAutoAlignment KEYWORD2 +setESFAutoAlignment KEYWORD2 + +getTimePulseParameters KEYWORD2 +setTimePulseParameters KEYWORD2 + +getAckAiding KEYWORD2 +setAckAiding KEYWORD2 + +getAopCfg KEYWORD2 +setAopCfg KEYWORD2 createKey KEYWORD2 getVal KEYWORD2 @@ -281,6 +303,15 @@ initPacketUBXNAVTIMELS KEYWORD2 getSurveyStatus KEYWORD2 initPacketUBXNAVSVIN KEYWORD2 +getNAVSAT KEYWORD2 +setAutoNAVSAT KEYWORD2 +setAutoNAVSATrate KEYWORD2 +setAutoNAVSATcallback KEYWORD2 +assumeAutoNAVSAT KEYWORD2 +initPacketUBXNAVSAT KEYWORD2 +flushNAVSAT KEYWORD2 +logNAVSAT KEYWORD2 + getRELPOSNED KEYWORD2 setAutoRELPOSNED KEYWORD2 setAutoRELPOSNEDrate KEYWORD2 @@ -290,6 +321,15 @@ initPacketUBXNAVRELPOSNED KEYWORD2 flushNAVRELPOSNED KEYWORD2 logNAVRELPOSNED KEYWORD2 +getAOPSTATUS KEYWORD2 +setAutoAOPSTATUS KEYWORD2 +setAutoAOPSTATUSrate KEYWORD2 +setAutoAOPSTATUScallback KEYWORD2 +assumeAutoAOPSTATUS KEYWORD2 +initPacketUBXAOPSTATUS KEYWORD2 +flushAOPSTATUS KEYWORD2 +logAOPSTATUS KEYWORD2 + getRXMSFRBX KEYWORD2 setAutoRXMSFRBX KEYWORD2 setAutoRXMSFRBXrate KEYWORD2 @@ -367,9 +407,6 @@ initPacketUBXESFRAW KEYWORD2 flushESFRAW KEYWORD2 logESFRAW KEYWORD2 -getESFAutoAlignment KEYWORD2 -setESFAutoAlignment KEYWORD2 - getHNRAtt KEYWORD2 getHNRATT KEYWORD2 setAutoHNRATT KEYWORD2 @@ -491,6 +528,9 @@ getRelPosAccN KEYWORD2 getRelPosAccE KEYWORD2 getRelPosAccD KEYWORD2 +getAOPSTATUSuseAOP KEYWORD2 +getAOPSTATUSstatus KEYWORD2 + getESFroll KEYWORD2 getESFpitch KEYWORD2 getESFyaw KEYWORD2 @@ -675,4 +715,14 @@ SFE_UBLOX_GNSS_ID_IMES LITERAL1 SFE_UBLOX_GNSS_ID_QZSS LITERAL1 SFE_UBLOX_GNSS_ID_GLONASS LITERAL1 -DAYS_SINCE_MONTH LITERAL1 +SFE_UBLOX_MGA_ASSIST_ACK_NO LITERAL1 +SFE_UBLOX_MGA_ASSIST_ACK_YES LITERAL1 +SFE_UBLOX_MGA_ASSIST_ACK_ENQUIRE LITERAL1 + +SFE_UBLOX_MGA_ACK_INFOCODE_ACCEPTED LITERAL1 +SFE_UBLOX_MGA_ACK_INFOCODE_NO_TIME LITERAL1 +SFE_UBLOX_MGA_ACK_INFOCODE_NOT_SUPPORTED LITERAL1 +SFE_UBLOX_MGA_ACK_INFOCODE_SIZE_MISMATCH LITERAL1 +SFE_UBLOX_MGA_ACK_INFOCODE_NOT_STORED LITERAL1 +SFE_UBLOX_MGA_ACK_INFOCODE_NOT_READY LITERAL1 +SFE_UBLOX_MGA_ACK_INFOCODE_TYPE_UNKNOWN LITERAL1 diff --git a/library.properties b/library.properties index 94897cd..08eb80d 100644 --- a/library.properties +++ b/library.properties @@ -1,5 +1,5 @@ name=SparkFun u-blox GNSS Arduino Library -version=2.0.18 +version=2.1.0 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 68130fa..a8e1e1f 100644 --- a/src/SparkFun_u-blox_GNSS_Arduino_Library.cpp +++ b/src/SparkFun_u-blox_GNSS_Arduino_Library.cpp @@ -234,6 +234,16 @@ void SFE_UBLOX_GNSS::end(void) packetUBXNAVSVIN = NULL; // Redundant? } + if (packetUBXNAVSAT != NULL) + { + if (packetUBXNAVSAT->callbackData != NULL) + { + delete packetUBXNAVSAT->callbackData; + } + delete packetUBXNAVSAT; + packetUBXNAVSAT = NULL; // Redundant? + } + if (packetUBXNAVRELPOSNED != NULL) { if (packetUBXNAVRELPOSNED->callbackData != NULL) @@ -244,6 +254,16 @@ void SFE_UBLOX_GNSS::end(void) packetUBXNAVRELPOSNED = NULL; // Redundant? } + if (packetUBXNAVAOPSTATUS != NULL) + { + if (packetUBXNAVAOPSTATUS->callbackData != NULL) + { + delete packetUBXNAVAOPSTATUS->callbackData; + } + delete packetUBXNAVAOPSTATUS; + packetUBXNAVAOPSTATUS = NULL; // Redundant? + } + if (packetUBXRXMSFRBX != NULL) { if (packetUBXRXMSFRBX->callbackData != NULL) @@ -334,6 +354,18 @@ void SFE_UBLOX_GNSS::end(void) packetUBXESFRAW = NULL; // Redundant? } + if (packetUBXMGAACK != NULL) + { + delete packetUBXMGAACK; + packetUBXMGAACK = NULL; // Redundant? + } + + if (packetUBXMGADBD != NULL) + { + delete packetUBXMGADBD; + packetUBXMGADBD = NULL; // Redundant? + } + if (packetUBXHNRATT != NULL) { if (packetUBXHNRATT->callbackData != NULL) @@ -1014,9 +1046,15 @@ bool SFE_UBLOX_GNSS::checkAutomatic(uint8_t Class, uint8_t ID) case UBX_NAV_SVIN: if (packetUBXNAVSVIN != NULL) result = true; break; + case UBX_NAV_SAT: + if (packetUBXNAVSAT != NULL) result = true; + break; case UBX_NAV_RELPOSNED: if (packetUBXNAVRELPOSNED != NULL) result = true; break; + case UBX_NAV_AOPSTATUS: + if (packetUBXNAVAOPSTATUS != NULL) result = true; + break; } } break; @@ -1075,6 +1113,19 @@ bool SFE_UBLOX_GNSS::checkAutomatic(uint8_t Class, uint8_t ID) } } break; + case UBX_CLASS_MGA: + { + switch (ID) + { + case UBX_MGA_ACK_DATA0: + if (packetUBXMGAACK != NULL) result = true; + break; + case UBX_MGA_DBD: + if (packetUBXMGADBD != NULL) result = true; + break; + } + } + break; case UBX_CLASS_HNR: { switch (ID) @@ -1144,9 +1195,15 @@ uint16_t SFE_UBLOX_GNSS::getMaxPayloadSize(uint8_t Class, uint8_t ID) case UBX_NAV_SVIN: maxSize = UBX_NAV_SVIN_LEN; break; + case UBX_NAV_SAT: + maxSize = UBX_NAV_SAT_MAX_LEN; + break; case UBX_NAV_RELPOSNED: maxSize = UBX_NAV_RELPOSNED_LEN_F9; break; + case UBX_NAV_AOPSTATUS: + maxSize = UBX_NAV_AOPSTATUS_LEN; + break; } } break; @@ -1205,6 +1262,19 @@ uint16_t SFE_UBLOX_GNSS::getMaxPayloadSize(uint8_t Class, uint8_t ID) } } break; + case UBX_CLASS_MGA: + { + switch (ID) + { + case UBX_MGA_ACK_DATA0: + maxSize = UBX_MGA_ACK_DATA0_LEN; + break; + case UBX_MGA_DBD: + maxSize = UBX_MGA_DBD_LEN; // UBX_MGA_DBD_LEN is actually a maximum length. The packets could be shorter than this. + break; + } + } + break; case UBX_CLASS_HNR: { switch (ID) @@ -1231,7 +1301,7 @@ void SFE_UBLOX_GNSS::process(uint8_t incoming, ubxPacket *incomingUBX, uint8_t r { if ((currentSentence == NONE) || (currentSentence == NMEA)) { - if (incoming == 0xB5) //UBX binary frames start with 0xB5, aka μ + if (incoming == UBX_SYNCH_1) //UBX binary frames start with 0xB5, aka μ { //This is the start of a binary sentence. Reset flags. //We still don't know the response class @@ -1263,9 +1333,9 @@ void SFE_UBLOX_GNSS::process(uint8_t incoming, ubxPacket *incomingUBX, uint8_t r if (currentSentence == UBX) { //Decide what type of response this is - if ((ubxFrameCounter == 0) && (incoming != 0xB5)) //ISO 'μ' + if ((ubxFrameCounter == 0) && (incoming != UBX_SYNCH_1)) //ISO 'μ' currentSentence = NONE; //Something went wrong. Reset. - else if ((ubxFrameCounter == 1) && (incoming != 0x62)) //ASCII 'b' + else if ((ubxFrameCounter == 1) && (incoming != UBX_SYNCH_2)) //ASCII 'b' currentSentence = NONE; //Something went wrong. Reset. // Note to future self: // There may be some duplication / redundancy in the next few lines as processUBX will also @@ -2334,6 +2404,46 @@ void SFE_UBLOX_GNSS::processUBXpacket(ubxPacket *msg) packetUBXNAVSVIN->moduleQueried.moduleQueried.all = 0xFFFFFFFF; } } + else if (msg->id == UBX_NAV_SAT) // Note: length is variable + { + //Parse various byte fields into storage - but only if we have memory allocated for it + if (packetUBXNAVSAT != NULL) + { + packetUBXNAVSAT->data.header.iTOW = extractLong(msg, 0); + packetUBXNAVSAT->data.header.version = extractByte(msg, 4); + packetUBXNAVSAT->data.header.numSvs = extractByte(msg, 5); + + for (uint8_t i = 0; (i < UBX_NAV_SAT_MAX_BLOCKS) && (i < packetUBXNAVSAT->data.header.numSvs) + && ((((uint16_t)i) * 12) < (msg->len - 8)); i++) + { + uint16_t offset = (((uint16_t)i) * 12) + 8; + packetUBXNAVSAT->data.blocks[i].gnssId = extractByte(msg, offset + 0); + packetUBXNAVSAT->data.blocks[i].svId = extractByte(msg, offset + 1); + packetUBXNAVSAT->data.blocks[i].cno = extractByte(msg, offset + 2); + packetUBXNAVSAT->data.blocks[i].elev = extractSignedChar(msg, offset + 3); + packetUBXNAVSAT->data.blocks[i].azim = extractSignedInt(msg, offset + 4); + packetUBXNAVSAT->data.blocks[i].prRes = extractSignedInt(msg, offset + 6); + packetUBXNAVSAT->data.blocks[i].flags.all = extractLong(msg, offset + 8); + } + + //Mark all datums as fresh (not read before) + packetUBXNAVSAT->moduleQueried = true; + + //Check if we need to copy the data for the callback + if ((packetUBXNAVSAT->callbackData != NULL) // If RAM has been allocated for the copy of the data + && (packetUBXNAVSAT->automaticFlags.flags.bits.callbackCopyValid == false)) // AND the data is stale + { + memcpy(&packetUBXNAVSAT->callbackData->header.iTOW, &packetUBXNAVSAT->data.header.iTOW, sizeof(UBX_NAV_SAT_data_t)); + packetUBXNAVSAT->automaticFlags.flags.bits.callbackCopyValid = true; + } + + //Check if we need to copy the data into the file buffer + if (packetUBXNAVSAT->automaticFlags.flags.bits.addToFileBuffer) + { + storePacket(msg); + } + } + } else if (msg->id == UBX_NAV_RELPOSNED && ((msg->len == UBX_NAV_RELPOSNED_LEN) || (msg->len == UBX_NAV_RELPOSNED_LEN_F9))) { //Parse various byte fields into storage - but only if we have memory allocated for it @@ -2401,6 +2511,33 @@ void SFE_UBLOX_GNSS::processUBXpacket(ubxPacket *msg) } } } + else if (msg->id == UBX_NAV_AOPSTATUS && msg->len == UBX_NAV_AOPSTATUS_LEN) + { + //Parse various byte fields into storage - but only if we have memory allocated for it + if (packetUBXNAVAOPSTATUS != NULL) + { + packetUBXNAVAOPSTATUS->data.iTOW = extractLong(msg, 0); + packetUBXNAVAOPSTATUS->data.aopCfg.all = extractByte(msg, 4); + packetUBXNAVAOPSTATUS->data.status = extractByte(msg, 5); + + //Mark all datums as fresh (not read before) + packetUBXNAVAOPSTATUS->moduleQueried.moduleQueried.all = 0xFFFFFFFF; + + //Check if we need to copy the data for the callback + if ((packetUBXNAVAOPSTATUS->callbackData != NULL) // If RAM has been allocated for the copy of the data + && (packetUBXNAVAOPSTATUS->automaticFlags.flags.bits.callbackCopyValid == false)) // AND the data is stale + { + memcpy(&packetUBXNAVAOPSTATUS->callbackData->iTOW, &packetUBXNAVAOPSTATUS->data.iTOW, sizeof(UBX_NAV_AOPSTATUS_data_t)); + packetUBXNAVAOPSTATUS->automaticFlags.flags.bits.callbackCopyValid = true; + } + + //Check if we need to copy the data into the file buffer + if (packetUBXNAVAOPSTATUS->automaticFlags.flags.bits.addToFileBuffer) + { + storePacket(msg); + } + } + } break; case UBX_CLASS_RXM: if (msg->id == UBX_RXM_SFRBX) @@ -2715,6 +2852,99 @@ void SFE_UBLOX_GNSS::processUBXpacket(ubxPacket *msg) } } break; + case UBX_CLASS_MGA: + if (msg->id == UBX_MGA_ACK_DATA0 && msg->len == UBX_MGA_ACK_DATA0_LEN) + { + //Parse various byte fields into storage - but only if we have memory allocated for it + if (packetUBXMGAACK != NULL) + { + // Calculate how many ACKs are already stored in the ring buffer + uint8_t ackBufferContains; + if (packetUBXMGAACK->head >= packetUBXMGAACK->tail) // Check if wrap-around has occurred + { + // Wrap-around has not occurred so do a simple subtraction + ackBufferContains = packetUBXMGAACK->head - packetUBXMGAACK->tail; + } + else + { + // Wrap-around has occurred so do a simple subtraction but add in the buffer length (UBX_MGA_ACK_RINGBUFFER_LEN) + ackBufferContains = ((uint8_t)(((uint16_t)packetUBXMGAACK->head + (uint16_t)UBX_MGA_ACK_DATA0_RINGBUFFER_LEN) - (uint16_t)packetUBXMGAACK->tail)); + } + // Have we got space to store this ACK? + if (ackBufferContains < (UBX_MGA_ACK_DATA0_RINGBUFFER_LEN - 1)) + { + // Yes, we have, so store it + packetUBXMGAACK->data[packetUBXMGAACK->head].type = extractByte(msg, 0); + packetUBXMGAACK->data[packetUBXMGAACK->head].version = extractByte(msg, 1); + packetUBXMGAACK->data[packetUBXMGAACK->head].infoCode = extractByte(msg, 2); + packetUBXMGAACK->data[packetUBXMGAACK->head].msgId = extractByte(msg, 3); + packetUBXMGAACK->data[packetUBXMGAACK->head].msgPayloadStart[0] = extractByte(msg, 4); + packetUBXMGAACK->data[packetUBXMGAACK->head].msgPayloadStart[1] = extractByte(msg, 5); + packetUBXMGAACK->data[packetUBXMGAACK->head].msgPayloadStart[2] = extractByte(msg, 6); + packetUBXMGAACK->data[packetUBXMGAACK->head].msgPayloadStart[3] = extractByte(msg, 7); + // Increment the head + packetUBXMGAACK->head++; + if (packetUBXMGAACK->head == UBX_MGA_ACK_DATA0_RINGBUFFER_LEN) + packetUBXMGAACK->head = 0; + } + else + { + if ((_printDebug == true) || (_printLimitedDebug == true)) // This is important. Print this if doing limited debugging + { + _debugSerial->println(F("processUBXpacket: packetUBXMGAACK is full. ACK will be lost!")); + } + } + } + } + else if (msg->id == UBX_MGA_DBD && msg->len <= UBX_MGA_DBD_LEN) // Message length may be less than UBX_MGA_DBD_LEN. UBX_MGA_DBD_LEN is the maximum it will be. + { + //Parse various byte fields into storage - but only if we have memory allocated for it + if (packetUBXMGADBD != NULL) + { + // Calculate how many DBDs are already stored in the ring buffer + uint8_t dbdBufferContains; + if (packetUBXMGADBD->head >= packetUBXMGADBD->tail) // Check if wrap-around has occurred + { + // Wrap-around has not occurred so do a simple subtraction + dbdBufferContains = packetUBXMGADBD->head - packetUBXMGADBD->tail; + } + else + { + // Wrap-around has occurred so do a simple subtraction but add in the buffer length (UBX_MGA_DBD_RINGBUFFER_LEN) + dbdBufferContains = ((uint8_t)(((uint16_t)packetUBXMGADBD->head + (uint16_t)UBX_MGA_DBD_RINGBUFFER_LEN) - (uint16_t)packetUBXMGADBD->tail)); + } + // Have we got space to store this DBD? + if (dbdBufferContains < (UBX_MGA_DBD_RINGBUFFER_LEN - 1)) + { + // Yes, we have, so store it + // We need to save the entire message - header, payload and checksum + packetUBXMGADBD->data[packetUBXMGADBD->head].dbdEntryHeader1 = UBX_SYNCH_1; + packetUBXMGADBD->data[packetUBXMGADBD->head].dbdEntryHeader2 = UBX_SYNCH_2; + packetUBXMGADBD->data[packetUBXMGADBD->head].dbdEntryClass = UBX_CLASS_MGA; + packetUBXMGADBD->data[packetUBXMGADBD->head].dbdEntryID = UBX_MGA_DBD; + packetUBXMGADBD->data[packetUBXMGADBD->head].dbdEntryLenLSB = (uint8_t)(msg->len & 0xFF); // We need to store the length of the DBD entry. The entry itself does not contain a length... + packetUBXMGADBD->data[packetUBXMGADBD->head].dbdEntryLenMSB = (uint8_t)((msg->len >> 8) & 0xFF); + for (uint16_t i = 0; i < msg->len; i++) + { + packetUBXMGADBD->data[packetUBXMGADBD->head].dbdEntry[i] = extractByte(msg, i); + } + packetUBXMGADBD->data[packetUBXMGADBD->head].dbdEntryChecksumA = msg->checksumA; + packetUBXMGADBD->data[packetUBXMGADBD->head].dbdEntryChecksumB = msg->checksumB; + // Increment the head + packetUBXMGADBD->head++; + if (packetUBXMGADBD->head == UBX_MGA_DBD_RINGBUFFER_LEN) + packetUBXMGADBD->head = 0; + } + else + { + if ((_printDebug == true) || (_printLimitedDebug == true)) // This is important. Print this if doing limited debugging + { + _debugSerial->println(F("processUBXpacket: packetUBXMGADBD is full. DBD data will be lost!")); + } + } + } + } + break; case UBX_CLASS_HNR: if (msg->id == UBX_HNR_PVT && msg->len == UBX_HNR_PVT_LEN) { @@ -3691,6 +3921,17 @@ void SFE_UBLOX_GNSS::checkCallbacks(void) packetUBXNAVCLOCK->automaticFlags.flags.bits.callbackCopyValid = false; // Mark the data as stale } + if ((packetUBXNAVSAT != NULL) // If RAM has been allocated for message storage + && (packetUBXNAVSAT->callbackData != NULL) // If RAM has been allocated for the copy of the data + && (packetUBXNAVSAT->callbackPointer != NULL) // If the pointer to the callback has been defined + && (packetUBXNAVSAT->automaticFlags.flags.bits.callbackCopyValid == true)) // If the copy of the data is valid + { + // if (_printDebug == true) + // _debugSerial->println(F("checkCallbacks: calling callback for NAV SAT")); + packetUBXNAVSAT->callbackPointer(*packetUBXNAVSAT->callbackData); // Call the callback + packetUBXNAVSAT->automaticFlags.flags.bits.callbackCopyValid = false; // Mark the data as stale + } + if ((packetUBXNAVRELPOSNED != NULL) // If RAM has been allocated for message storage && (packetUBXNAVRELPOSNED->callbackData != NULL) // If RAM has been allocated for the copy of the data && (packetUBXNAVRELPOSNED->callbackPointer != NULL) // If the pointer to the callback has been defined @@ -3702,6 +3943,17 @@ void SFE_UBLOX_GNSS::checkCallbacks(void) packetUBXNAVRELPOSNED->automaticFlags.flags.bits.callbackCopyValid = false; // Mark the data as stale } + if ((packetUBXNAVAOPSTATUS != NULL) // If RAM has been allocated for message storage + && (packetUBXNAVAOPSTATUS->callbackData != NULL) // If RAM has been allocated for the copy of the data + && (packetUBXNAVAOPSTATUS->callbackPointer != NULL) // If the pointer to the callback has been defined + && (packetUBXNAVAOPSTATUS->automaticFlags.flags.bits.callbackCopyValid == true)) // If the copy of the data is valid + { + // if (_printDebug == true) + // _debugSerial->println(F("checkCallbacks: calling callback for NAV AOPSTATUS")); + packetUBXNAVAOPSTATUS->callbackPointer(*packetUBXNAVAOPSTATUS->callbackData); // Call the callback + packetUBXNAVAOPSTATUS->automaticFlags.flags.bits.callbackCopyValid = false; // Mark the data as stale + } + if ((packetUBXRXMSFRBX != NULL) // If RAM has been allocated for message storage && (packetUBXRXMSFRBX->callbackData != NULL) // If RAM has been allocated for the copy of the data && (packetUBXRXMSFRBX->callbackPointer != NULL) // If the pointer to the callback has been defined @@ -3931,6 +4183,776 @@ bool SFE_UBLOX_GNSS::pushRawData(uint8_t *dataBytes, size_t numDataBytes, bool s } } +// Push MGA AssistNow data to the module. +// Check for UBX-MGA-ACK responses if required (if mgaAck is YES or ENQUIRE). +// Wait for maxWait millis after sending each packet (if mgaAck is NO). +// Return how many bytes were pushed successfully. +// If skipTime is true, any UBX-MGA-INI-TIME_UTC or UBX-MGA-INI-TIME_GNSS packets found in the data will be skipped, +// allowing the user to override with their own time data with setUTCTimeAssistance. +size_t SFE_UBLOX_GNSS::pushAssistNowData(const String &dataBytes, size_t numDataBytes, sfe_ublox_mga_assist_ack_e mgaAck, uint16_t maxWait) +{ + return (pushAssistNowDataInternal(0, false, (const uint8_t *)dataBytes.c_str(), numDataBytes, mgaAck, maxWait)); +} +size_t SFE_UBLOX_GNSS::pushAssistNowData(const uint8_t *dataBytes, size_t numDataBytes, sfe_ublox_mga_assist_ack_e mgaAck, uint16_t maxWait) +{ + return (pushAssistNowDataInternal(0, false, dataBytes, numDataBytes, mgaAck, maxWait)); +} +size_t SFE_UBLOX_GNSS::pushAssistNowData(bool skipTime, const String &dataBytes, size_t numDataBytes, sfe_ublox_mga_assist_ack_e mgaAck, uint16_t maxWait) +{ + return (pushAssistNowDataInternal(0, skipTime, (const uint8_t *)dataBytes.c_str(), numDataBytes, mgaAck, maxWait)); +} +size_t SFE_UBLOX_GNSS::pushAssistNowData(bool skipTime, const uint8_t *dataBytes, size_t numDataBytes, sfe_ublox_mga_assist_ack_e mgaAck, uint16_t maxWait) +{ + return (pushAssistNowDataInternal(0, skipTime, dataBytes, numDataBytes, mgaAck, maxWait)); +} +size_t SFE_UBLOX_GNSS::pushAssistNowData(size_t offset, bool skipTime, const String &dataBytes, size_t numDataBytes, sfe_ublox_mga_assist_ack_e mgaAck, uint16_t maxWait) +{ + return (pushAssistNowDataInternal(offset, skipTime, (const uint8_t *)dataBytes.c_str(), numDataBytes, mgaAck, maxWait)); +} +size_t SFE_UBLOX_GNSS::pushAssistNowData(size_t offset, bool skipTime, const uint8_t *dataBytes, size_t numDataBytes, sfe_ublox_mga_assist_ack_e mgaAck, uint16_t maxWait) +{ + return (pushAssistNowDataInternal(offset, skipTime, dataBytes, numDataBytes, mgaAck, maxWait)); +} +size_t SFE_UBLOX_GNSS::pushAssistNowDataInternal(size_t offset, bool skipTime, const uint8_t *dataBytes, size_t numDataBytes, sfe_ublox_mga_assist_ack_e mgaAck, uint16_t maxWait) +{ + size_t dataPtr = offset; // Pointer into dataBytes + + if ((offset >= numDataBytes) || (offset < 0)) // Sanity check. Return now if offset is invalid. + { +#ifndef SFE_UBLOX_REDUCED_PROG_MEM + if ((_printDebug == true) || (_printLimitedDebug == true)) // This is important. Print this if doing limited debugging + { + _debugSerial->print(F("pushAssistNowData: offset (")); + _debugSerial->print(offset); + _debugSerial->println(F(") is invalid! Aborting...")); + } +#endif + return ((size_t)0); + } + + if (numDataBytes < 0) // Sanity check. Return now if numDataBytes is negative. + { +#ifndef SFE_UBLOX_REDUCED_PROG_MEM + if ((_printDebug == true) || (_printLimitedDebug == true)) // This is important. Print this if doing limited debugging + { + _debugSerial->println(F("pushAssistNowData: numDataBytes is negative! Aborting...")); + } +#endif + return ((size_t)0); + } + + size_t packetsProcessed = 0; // Keep count of how many packets have been processed + size_t bytesPushed = 0; // Keep count + + bool checkForAcks = (mgaAck == SFE_UBLOX_MGA_ASSIST_ACK_YES); // If mgaAck is YES, always check for Acks + + // If mgaAck is ENQUIRE, we need to check UBX-CFG-NAVX5 ackAiding to determine if UBX-MGA-ACK's are expected + if (mgaAck == SFE_UBLOX_MGA_ASSIST_ACK_ENQUIRE) + { + uint8_t ackAiding = getAckAiding(maxWait); // Enquire if we should expect Acks + if (ackAiding == 1) + checkForAcks = true; + +#ifndef SFE_UBLOX_REDUCED_PROG_MEM + if ((_printDebug == true) || (_printLimitedDebug == true)) // This is important. Print this if doing limited debugging + { + _debugSerial->print(F("pushAssistNowData: mgaAck is ENQUIRE. getAckAiding returned ")); + _debugSerial->println(ackAiding); + } +#endif + } + + // If checkForAcks is true, then we need to set up storage for the UBX-MGA-ACK-DATA0 messages + if (checkForAcks) + { + if (packetUBXMGAACK == NULL) initPacketUBXMGAACK(); //Check that RAM has been allocated for the MGA_ACK data + if (packetUBXMGAACK == NULL) //Bail if the RAM allocation failed + return (0); + } + + while (dataPtr < numDataBytes) // Keep going until we have processed all the bytes + { + // Start by checking the validity of the packet being pointed to + bool dataIsOK = true; + + dataIsOK &= (*(dataBytes + dataPtr + 0) == UBX_SYNCH_1); // Check for 0xB5 + dataIsOK &= (*(dataBytes + dataPtr + 1) == UBX_SYNCH_2); // Check for 0x62 + dataIsOK &= (*(dataBytes + dataPtr + 2) == UBX_CLASS_MGA); // Check for class UBX-MGA + + size_t packetLength = ((size_t)*(dataBytes + dataPtr + 4)) | (((size_t)*(dataBytes + dataPtr + 5)) << 8); // Extract the length + + uint8_t checksumA = 0; + uint8_t checksumB = 0; + // Calculate the checksum bytes + // Keep going until the end of the packet is reached (payloadPtr == (dataPtr + packetLength)) + // or we reach the end of the AssistNow data (payloadPtr == numDataBytes) + for (size_t payloadPtr = dataPtr + ((size_t)2); (payloadPtr < (dataPtr + packetLength + ((size_t)6))) && (payloadPtr < numDataBytes); payloadPtr++) + { + checksumA += *(dataBytes + payloadPtr); + checksumB += checksumA; + } + // Check the checksum bytes + dataIsOK &= (checksumA == *(dataBytes + dataPtr + packetLength + ((size_t)6))); + dataIsOK &= (checksumB == *(dataBytes + dataPtr + packetLength + ((size_t)7))); + + dataIsOK &= ((dataPtr + packetLength + ((size_t)8)) <= numDataBytes); // Check we haven't overrun + + // If the data is valid, push it + if (dataIsOK) + { + // Check if this is time assistance data which should be skipped + if ((skipTime) && ((*(dataBytes + dataPtr + 3) == UBX_MGA_INI_TIME_UTC) || (*(dataBytes + dataPtr + 3) == UBX_MGA_INI_TIME_GNSS))) + { +#ifndef SFE_UBLOX_REDUCED_PROG_MEM + if ((_printDebug == true) || (_printLimitedDebug == true)) // This is important. Print this if doing limited debugging + { + _debugSerial->print(F("pushAssistNowData: skipped INI_TIME ID 0x")); + if (*(dataBytes + dataPtr + 3) < 0x10) + _debugSerial->print(F("0")); + _debugSerial->println(*(dataBytes + dataPtr + 3), HEX); + } +#endif + } + else + { + bool pushResult = pushRawData((uint8_t *)(dataBytes + dataPtr), packetLength + ((size_t)8)); // Push the data + + if (pushResult) + bytesPushed += packetLength + ((size_t)8); // Increment bytesPushed if the push was successful + + if ((_printDebug == true) || (_printLimitedDebug == true)) // This is important. Print this if doing limited debugging + { + _debugSerial->print(F("pushAssistNowData: packet ID 0x")); + if (*(dataBytes + dataPtr + 3) < 0x10) + _debugSerial->print(F("0")); + _debugSerial->print(*(dataBytes + dataPtr + 3), HEX); + _debugSerial->print(F(" length ")); + _debugSerial->println(packetLength); + } + + if (checkForAcks) + { + unsigned long startTime = millis(); + bool keepGoing = true; + while (keepGoing && (millis() < (startTime + maxWait))) // Keep checking for the ACK until we time out + { + checkUblox(); + if (packetUBXMGAACK->head != packetUBXMGAACK->tail) // Does the MGA ACK ringbuffer contain any ACK's? + { + bool dataAckd = true; // Check if we've received the correct ACK + dataAckd &= (packetUBXMGAACK->data[packetUBXMGAACK->tail].msgId == *(dataBytes + dataPtr + 3)); // Check if the message ID matches + dataAckd &= (packetUBXMGAACK->data[packetUBXMGAACK->tail].msgPayloadStart[0] == *(dataBytes + dataPtr + 6)); // Check if the first four data bytes match + dataAckd &= (packetUBXMGAACK->data[packetUBXMGAACK->tail].msgPayloadStart[1] == *(dataBytes + dataPtr + 7)); + dataAckd &= (packetUBXMGAACK->data[packetUBXMGAACK->tail].msgPayloadStart[2] == *(dataBytes + dataPtr + 8)); + dataAckd &= (packetUBXMGAACK->data[packetUBXMGAACK->tail].msgPayloadStart[3] == *(dataBytes + dataPtr + 9)); + + if (dataAckd) // Is this the ACK we are looking for? + { + if ((packetUBXMGAACK->data[packetUBXMGAACK->tail].type == (uint8_t)1) && (packetUBXMGAACK->data[packetUBXMGAACK->tail].infoCode == (uint8_t)SFE_UBLOX_MGA_ACK_INFOCODE_ACCEPTED)) + { +#ifndef SFE_UBLOX_REDUCED_PROG_MEM + if ((_printDebug == true) || (_printLimitedDebug == true)) // This is important. Print this if doing limited debugging + { + _debugSerial->print(F("pushAssistNowData: packet was accepted after ")); + _debugSerial->print(millis() - startTime); + _debugSerial->println(F(" ms")); + } +#endif + packetsProcessed++; + } + else + { +#ifndef SFE_UBLOX_REDUCED_PROG_MEM + if ((_printDebug == true) || (_printLimitedDebug == true)) // This is important. Print this if doing limited debugging + { + _debugSerial->print(F("pushAssistNowData: packet was _not_ accepted. infoCode is ")); + _debugSerial->println(packetUBXMGAACK->data[packetUBXMGAACK->tail].infoCode); + } +#endif + } + keepGoing = false; + } + // Increment the tail + packetUBXMGAACK->tail++; + if (packetUBXMGAACK->tail == UBX_MGA_ACK_DATA0_RINGBUFFER_LEN) + packetUBXMGAACK->tail = 0; + } + } + if (keepGoing) // If keepGoing is still true, we must have timed out + { + if ((_printDebug == true) || (_printLimitedDebug == true)) // This is important. Print this if doing limited debugging + { + _debugSerial->println(F("pushAssistNowData: packet ack timed out!")); + } + } + } + else + { + // We are not checking for Acks, so let's assume the send was successful? + packetsProcessed++; + // We are not checking for Acks, so delay for maxWait millis unless we've reached the end of the data + if ((dataPtr + packetLength + ((size_t)8)) < numDataBytes) + { + delay(maxWait); + } + } + } + + dataPtr += packetLength + ((size_t)8); // Point to the next message + } + else + { + +#ifndef SFE_UBLOX_REDUCED_PROG_MEM + // The data was invalid. Send a debug message and then try to find the next 0xB5 + if ((_printDebug == true) || (_printLimitedDebug == true)) // This is important. Print this if doing limited debugging + { + _debugSerial->print(F("pushAssistNowData: bad data - ignored! dataPtr is ")); + _debugSerial->println(dataPtr); + } +#endif + + while ((dataPtr < numDataBytes) && (*(dataBytes + ++dataPtr) != UBX_SYNCH_1)) + { + ; // Increment dataPtr until we are pointing at the next 0xB5 - or we reach the end of the data + } + } + } + +#ifndef SFE_UBLOX_REDUCED_PROG_MEM + if ((_printDebug == true) || (_printLimitedDebug == true)) // This is important. Print this if doing limited debugging + { + _debugSerial->print(F("pushAssistNowData: packetsProcessed: ")); + _debugSerial->println(packetsProcessed); + } +#endif + + return (bytesPushed); // Return the number of valid bytes successfully pushed +} + +// PRIVATE: Allocate RAM for packetUBXMGAACK and initialize it +bool SFE_UBLOX_GNSS::initPacketUBXMGAACK() +{ + packetUBXMGAACK = new UBX_MGA_ACK_DATA0_t; //Allocate RAM for the main struct + if (packetUBXMGAACK == NULL) + { + if ((_printDebug == true) || (_printLimitedDebug == true)) // This is important. Print this if doing limited debugging + _debugSerial->println(F("initPacketUBXMGAACK: RAM alloc failed!")); + return (false); + } + packetUBXMGAACK->head = 0; // Initialize the ring buffer pointers + packetUBXMGAACK->tail = 0; + return (true); +} + +// Provide initial time assistance +bool SFE_UBLOX_GNSS::setUTCTimeAssistance(uint16_t year, uint8_t month, uint8_t day, uint8_t hour, uint8_t minute, uint8_t second, uint32_t nanos, uint16_t tAccS, uint32_t tAccNs, uint8_t source, sfe_ublox_mga_assist_ack_e mgaAck, uint16_t maxWait) +{ + uint8_t iniTimeUTC[32]; // Create the UBX-MGA-INI-TIME_UTC message by hand + memset(iniTimeUTC, 0x00, 32); // Set all unused / reserved bytes and the checksum to zero + + iniTimeUTC[0] = UBX_SYNCH_1; // Sync char 1 + iniTimeUTC[1] = UBX_SYNCH_2; // Sync char 2 + iniTimeUTC[2] = UBX_CLASS_MGA; // Class + iniTimeUTC[3] = UBX_MGA_INI_TIME_UTC; // ID + iniTimeUTC[4] = 24; // Length LSB + iniTimeUTC[5] = 0x00; // Length MSB + iniTimeUTC[6] = 0x10; // type + iniTimeUTC[7] = 0x00; // version + iniTimeUTC[8] = source; // ref (source) + iniTimeUTC[9] = 0x80; // leapSecs. Set to 0x80 = unknown + iniTimeUTC[10] = (uint8_t)(year & 0xFF); // year LSB + iniTimeUTC[11] = (uint8_t)(year >> 8); // year MSB + iniTimeUTC[12] = month; // month starting at 1 + iniTimeUTC[13] = day; // day starting at 1 + iniTimeUTC[14] = hour; // hour 0:23 + iniTimeUTC[15] = minute; // minute 0:59 + iniTimeUTC[16] = second; // seconds 0:59 + iniTimeUTC[18] = (uint8_t)(nanos & 0xFF); // nanoseconds LSB + iniTimeUTC[19] = (uint8_t)((nanos >> 8) & 0xFF); + iniTimeUTC[20] = (uint8_t)((nanos >> 16) & 0xFF); + iniTimeUTC[21] = (uint8_t)(nanos >> 24); // nanoseconds MSB + iniTimeUTC[22] = (uint8_t)(tAccS & 0xFF); // seconds part of the accuracy LSB + iniTimeUTC[23] = (uint8_t)(tAccS >> 8); // seconds part of the accuracy MSB + iniTimeUTC[26] = (uint8_t)(tAccNs & 0xFF); // nanoseconds part of the accuracy LSB + iniTimeUTC[27] = (uint8_t)((tAccNs >> 8) & 0xFF); + iniTimeUTC[28] = (uint8_t)((tAccNs >> 16) & 0xFF); + iniTimeUTC[29] = (uint8_t)(tAccNs >> 24); // nanoseconds part of the accuracy MSB + + for (uint8_t i = 2; i < 30; i++) // Calculate the checksum + { + iniTimeUTC[30] += iniTimeUTC[i]; + iniTimeUTC[31] += iniTimeUTC[30]; + } + + // Return true if the one packet was pushed successfully + return (pushAssistNowDataInternal(0, false, iniTimeUTC, 32, mgaAck, maxWait) == 32); +} + +// Provide initial position assistance +// The units for ecefX/Y/Z and posAcc (stddev) are cm. +bool SFE_UBLOX_GNSS::setPositionAssistanceXYZ(int32_t ecefX, int32_t ecefY, int32_t ecefZ, uint32_t posAcc, sfe_ublox_mga_assist_ack_e mgaAck, uint16_t maxWait) +{ + uint8_t iniPosXYZ[28]; // Create the UBX-MGA-INI-POS_XYZ message by hand + memset(iniPosXYZ, 0x00, 28); // Set all unused / reserved bytes and the checksum to zero + + iniPosXYZ[0] = UBX_SYNCH_1; // Sync char 1 + iniPosXYZ[1] = UBX_SYNCH_2; // Sync char 2 + iniPosXYZ[2] = UBX_CLASS_MGA; // Class + iniPosXYZ[3] = UBX_MGA_INI_POS_XYZ; // ID + iniPosXYZ[4] = 20; // Length LSB + iniPosXYZ[5] = 0x00; // Length MSB + iniPosXYZ[6] = 0x00; // type + iniPosXYZ[7] = 0x00; // version + + union // Use a union to convert from int32_t to uint32_t + { + int32_t signedLong; + uint32_t unsignedLong; + } signedUnsigned; + + signedUnsigned.signedLong = ecefX; + iniPosXYZ[10] = (uint8_t)(signedUnsigned.unsignedLong & 0xFF); // LSB + iniPosXYZ[11] = (uint8_t)((signedUnsigned.unsignedLong >> 8) & 0xFF); + iniPosXYZ[12] = (uint8_t)((signedUnsigned.unsignedLong >> 16) & 0xFF); + iniPosXYZ[13] = (uint8_t)(signedUnsigned.unsignedLong >> 24); // MSB + + signedUnsigned.signedLong = ecefY; + iniPosXYZ[14] = (uint8_t)(signedUnsigned.unsignedLong & 0xFF); // LSB + iniPosXYZ[15] = (uint8_t)((signedUnsigned.unsignedLong >> 8) & 0xFF); + iniPosXYZ[16] = (uint8_t)((signedUnsigned.unsignedLong >> 16) & 0xFF); + iniPosXYZ[17] = (uint8_t)(signedUnsigned.unsignedLong >> 24); // MSB + + signedUnsigned.signedLong = ecefZ; + iniPosXYZ[18] = (uint8_t)(signedUnsigned.unsignedLong & 0xFF); // LSB + iniPosXYZ[19] = (uint8_t)((signedUnsigned.unsignedLong >> 8) & 0xFF); + iniPosXYZ[20] = (uint8_t)((signedUnsigned.unsignedLong >> 16) & 0xFF); + iniPosXYZ[21] = (uint8_t)(signedUnsigned.unsignedLong >> 24); // MSB + + iniPosXYZ[22] = (uint8_t)(posAcc & 0xFF); // LSB + iniPosXYZ[23] = (uint8_t)((posAcc >> 8) & 0xFF); + iniPosXYZ[24] = (uint8_t)((posAcc >> 16) & 0xFF); + iniPosXYZ[25] = (uint8_t)(posAcc >> 24); // MSB + + for (uint8_t i = 2; i < 26; i++) // Calculate the checksum + { + iniPosXYZ[26] += iniPosXYZ[i]; + iniPosXYZ[27] += iniPosXYZ[26]; + } + + // Return true if the one packet was pushed successfully + return (pushAssistNowDataInternal(0, false, iniPosXYZ, 28, mgaAck, maxWait) == 28); +} + +// The units for lat and lon are degrees * 1e-7 (WGS84) +// The units for alt (WGS84) and posAcc (stddev) are cm. +bool SFE_UBLOX_GNSS::setPositionAssistanceLLH(int32_t lat, int32_t lon, int32_t alt, uint32_t posAcc, sfe_ublox_mga_assist_ack_e mgaAck, uint16_t maxWait) +{ + uint8_t iniPosLLH[28]; // Create the UBX-MGA-INI-POS_LLH message by hand + memset(iniPosLLH, 0x00, 28); // Set all unused / reserved bytes and the checksum to zero + + iniPosLLH[0] = UBX_SYNCH_1; // Sync char 1 + iniPosLLH[1] = UBX_SYNCH_2; // Sync char 2 + iniPosLLH[2] = UBX_CLASS_MGA; // Class + iniPosLLH[3] = UBX_MGA_INI_POS_LLH; // ID + iniPosLLH[4] = 20; // Length LSB + iniPosLLH[5] = 0x00; // Length MSB + iniPosLLH[6] = 0x01; // type + iniPosLLH[7] = 0x00; // version + + union // Use a union to convert from int32_t to uint32_t + { + int32_t signedLong; + uint32_t unsignedLong; + } signedUnsigned; + + signedUnsigned.signedLong = lat; + iniPosLLH[10] = (uint8_t)(signedUnsigned.unsignedLong & 0xFF); // LSB + iniPosLLH[11] = (uint8_t)((signedUnsigned.unsignedLong >> 8) & 0xFF); + iniPosLLH[12] = (uint8_t)((signedUnsigned.unsignedLong >> 16) & 0xFF); + iniPosLLH[13] = (uint8_t)(signedUnsigned.unsignedLong >> 24); // MSB + + signedUnsigned.signedLong = lon; + iniPosLLH[14] = (uint8_t)(signedUnsigned.unsignedLong & 0xFF); // LSB + iniPosLLH[15] = (uint8_t)((signedUnsigned.unsignedLong >> 8) & 0xFF); + iniPosLLH[16] = (uint8_t)((signedUnsigned.unsignedLong >> 16) & 0xFF); + iniPosLLH[17] = (uint8_t)(signedUnsigned.unsignedLong >> 24); // MSB + + signedUnsigned.signedLong = alt; + iniPosLLH[18] = (uint8_t)(signedUnsigned.unsignedLong & 0xFF); // LSB + iniPosLLH[19] = (uint8_t)((signedUnsigned.unsignedLong >> 8) & 0xFF); + iniPosLLH[20] = (uint8_t)((signedUnsigned.unsignedLong >> 16) & 0xFF); + iniPosLLH[21] = (uint8_t)(signedUnsigned.unsignedLong >> 24); // MSB + + iniPosLLH[22] = (uint8_t)(posAcc & 0xFF); // LSB + iniPosLLH[23] = (uint8_t)((posAcc >> 8) & 0xFF); + iniPosLLH[24] = (uint8_t)((posAcc >> 16) & 0xFF); + iniPosLLH[25] = (uint8_t)(posAcc >> 24); // MSB + + for (uint8_t i = 2; i < 26; i++) // Calculate the checksum + { + iniPosLLH[26] += iniPosLLH[i]; + iniPosLLH[27] += iniPosLLH[26]; + } + + // Return true if the one packet was pushed successfully + return (pushAssistNowDataInternal(0, false, iniPosLLH, 28, mgaAck, maxWait) == 28); +} + +// Find the start of the AssistNow Offline (UBX_MGA_ANO) data for the chosen day +// The daysIntoFture parameter makes it easy to get the data for (e.g.) tomorrow based on today's date +// Returns numDataBytes if unsuccessful +// TO DO: enhance this so it will find the nearest data for the chosen day - instead of an exact match +size_t SFE_UBLOX_GNSS::findMGAANOForDate(const String &dataBytes, size_t numDataBytes, uint16_t year, uint8_t month, uint8_t day, uint8_t daysIntoFuture) +{ + return (findMGAANOForDateInternal((const uint8_t *)dataBytes.c_str(), numDataBytes, year, month, day, daysIntoFuture)); +} +size_t SFE_UBLOX_GNSS::findMGAANOForDate(const uint8_t *dataBytes, size_t numDataBytes, uint16_t year, uint8_t month, uint8_t day, uint8_t daysIntoFuture) +{ + return (findMGAANOForDateInternal(dataBytes, numDataBytes, year, month, day, daysIntoFuture)); +} +size_t SFE_UBLOX_GNSS::findMGAANOForDateInternal(const uint8_t *dataBytes, size_t numDataBytes, uint16_t year, uint8_t month, uint8_t day, uint8_t daysIntoFuture) +{ + size_t dataPtr = 0; // Pointer into dataBytes + bool dateFound = false; // Flag to indicate when the date has been found + + // Calculate matchDay, matchMonth and matchYear + uint8_t matchDay = day; + uint8_t matchMonth = month; + uint8_t matchYear = (uint8_t)(year - 2000); + + // Add on daysIntoFuture + uint8_t daysIntoFutureCopy = daysIntoFuture; + while (daysIntoFutureCopy > 0) + { + matchDay++; + daysIntoFutureCopy--; + switch (matchMonth) + { + case 1: + case 3: + case 5: + case 7: + case 8: + case 10: + case 12: + if (matchDay == 32) + { + matchDay = 1; + matchMonth++; + if (matchMonth == 13) + { + matchMonth = 1; + matchYear++; + } + } + break; + case 4: + case 6: + case 9: + case 11: + if (matchDay == 31) + { + matchDay = 1; + matchMonth++; + } + break; + default: // February + if (((matchYear % 4) == 0) && (matchDay == 30)) + { + matchDay = 1; + matchMonth++; + } + else if (((matchYear % 4) > 0) && (matchDay == 29)) + { + matchDay = 1; + matchMonth++; + } + break; + } + } + + while ((!dateFound) && (dataPtr < numDataBytes)) // Keep going until we have found the date or processed all the bytes + { + // Start by checking the validity of the packet being pointed to + bool dataIsOK = true; + + dataIsOK &= (*(dataBytes + dataPtr + 0) == UBX_SYNCH_1); // Check for 0xB5 + dataIsOK &= (*(dataBytes + dataPtr + 1) == UBX_SYNCH_2); // Check for 0x62 + dataIsOK &= (*(dataBytes + dataPtr + 2) == UBX_CLASS_MGA); // Check for class UBX-MGA + + size_t packetLength = ((size_t)*(dataBytes + dataPtr + 4)) | (((size_t)*(dataBytes + dataPtr + 5)) << 8); // Extract the length + + uint8_t checksumA = 0; + uint8_t checksumB = 0; + // Calculate the checksum bytes + // Keep going until the end of the packet is reached (payloadPtr == (dataPtr + packetLength)) + // or we reach the end of the AssistNow data (payloadPtr == numDataBytes) + for (size_t payloadPtr = dataPtr + ((size_t)2); (payloadPtr < (dataPtr + packetLength + ((size_t)6))) && (payloadPtr < numDataBytes); payloadPtr++) + { + checksumA += *(dataBytes + payloadPtr); + checksumB += checksumA; + } + // Check the checksum bytes + dataIsOK &= (checksumA == *(dataBytes + dataPtr + packetLength + ((size_t)6))); + dataIsOK &= (checksumB == *(dataBytes + dataPtr + packetLength + ((size_t)7))); + + dataIsOK &= ((dataPtr + packetLength + ((size_t)8)) <= numDataBytes); // Check we haven't overrun + + // If the data is valid, check for a date match + if (dataIsOK) + { + if ((*(dataBytes + dataPtr + 3) == UBX_MGA_ANO) + && (*(dataBytes + dataPtr + 10) == matchYear) + && (*(dataBytes + dataPtr + 11) == matchMonth) + && (*(dataBytes + dataPtr + 12) == matchDay)) + { +#ifndef SFE_UBLOX_REDUCED_PROG_MEM + if ((_printDebug == true) || (_printLimitedDebug == true)) // This is important. Print this if doing limited debugging + { + _debugSerial->print(F("findMGAANOForDate: found date match at location ")); + _debugSerial->println(dataPtr); + } +#endif + dateFound = true; + } + else + { + // The data is valid, but these are not the droids we are looking for... + dataPtr += packetLength + ((size_t)8); // Point to the next message + } + } + else + { + +#ifndef SFE_UBLOX_REDUCED_PROG_MEM + // The data was invalid. Send a debug message and then try to find the next 0xB5 + if ((_printDebug == true) || (_printLimitedDebug == true)) // This is important. Print this if doing limited debugging + { + _debugSerial->print(F("findMGAANOForDate: bad data - ignored! dataPtr is ")); + _debugSerial->println(dataPtr); + } +#endif + + while ((dataPtr < numDataBytes) && (*(dataBytes + ++dataPtr) != UBX_SYNCH_1)) + { + ; // Increment dataPtr until we are pointing at the next 0xB5 - or we reach the end of the data + } + } + } + + return (dataPtr); +} + +// Read the whole navigation data base. The receiver will send all available data from its internal database. +// Data is written to dataBytes. Set maxNumDataBytes to the (maximum) size of dataBytes. +// If the database exceeds maxNumDataBytes, the excess bytes will be lost. +// The function returns the number of database bytes written to dataBytes. +// The return value will be equal to maxNumDataBytes if excess data was received. +// The function will timeout after maxWait milliseconds - in case the final UBX-MGA-ACK was missed. +size_t SFE_UBLOX_GNSS::readNavigationDatabase(uint8_t *dataBytes, size_t maxNumDataBytes, uint16_t maxWait) +{ + // Allocate RAM to store the MGA ACK message + if (packetUBXMGAACK == NULL) initPacketUBXMGAACK(); //Check that RAM has been allocated for the MGA_ACK data + if (packetUBXMGAACK == NULL) //Bail if the RAM allocation failed + { +#ifndef SFE_UBLOX_REDUCED_PROG_MEM + if (_printDebug == true) + { + _debugSerial->println(F("readNavigationDatabase: packetUBXMGAACK RAM allocation failed!")); + } +#endif + return ((size_t)0); + } + if (packetUBXMGAACK->head != packetUBXMGAACK->tail) // Does the MGA ACK ringbuffer contain any data? + { +#ifndef SFE_UBLOX_REDUCED_PROG_MEM + if (_printDebug == true) + { + _debugSerial->println(F("readNavigationDatabase: packetUBXMGAACK contains unprocessed data. Clearing it.")); + } +#endif + packetUBXMGAACK->tail = packetUBXMGAACK->head; // Clear the buffer by setting the tail equal to the head + } + + // Allocate RAM to store the MGA DBD messages + if (packetUBXMGADBD == NULL) initPacketUBXMGADBD(); //Check that RAM has been allocated for the MGA_DBD data + if (packetUBXMGADBD == NULL) //Bail if the RAM allocation failed + { +#ifndef SFE_UBLOX_REDUCED_PROG_MEM + if ((_printDebug == true) || (_printLimitedDebug == true)) // This is important. Print this if doing limited debugging + { + _debugSerial->println(F("readNavigationDatabase: packetUBXMGADBD RAM allocation failed!")); + } +#endif + return ((size_t)0); + } + if (packetUBXMGADBD->head != packetUBXMGADBD->tail) // Does the MGA DBD ringbuffer contain any data? + { +#ifndef SFE_UBLOX_REDUCED_PROG_MEM + if (_printDebug == true) + { + _debugSerial->println(F("readNavigationDatabase: packetUBXMGADBD contains unprocessed data. Clearing it.")); + } +#endif + packetUBXMGADBD->tail = packetUBXMGADBD->head; // Clear the buffer by setting the tail equal to the head + } + + // Record what ackAiding is currently set to so we can restore it + uint8_t currentAckAiding = getAckAiding(); + if (currentAckAiding == 255) + currentAckAiding = 0; // If the get failed, disable the ACKs when returning + // Enable ackAiding + setAckAiding(1); + + // Record what i2cPollingWait is currently set to so we can restore it + uint8_t currentI2cPollingWait = i2cPollingWait; + // Set the I2C polling wait to 1ms + i2cPollingWait = 1; + + // Construct the poll message: + uint8_t pollNaviDatabase[8]; // Create the UBX-MGA-DBD message by hand + memset(pollNaviDatabase, 0x00, 8); // Set all unused / reserved bytes and the checksum to zero + + pollNaviDatabase[0] = UBX_SYNCH_1; // Sync char 1 + pollNaviDatabase[1] = UBX_SYNCH_2; // Sync char 2 + pollNaviDatabase[2] = UBX_CLASS_MGA; // Class + pollNaviDatabase[3] = UBX_MGA_DBD; // ID + pollNaviDatabase[4] = 0x00; // Length LSB + pollNaviDatabase[5] = 0x00; // Length MSB + + for (uint8_t i = 2; i < 6; i++) // Calculate the checksum + { + pollNaviDatabase[6] += pollNaviDatabase[i]; + pollNaviDatabase[7] += pollNaviDatabase[6]; + } + + // Push the poll message to the module. + // Do not Wait for an ACK - the DBD data will start arriving immediately. + size_t pushResult = pushAssistNowDataInternal(0, false, pollNaviDatabase, (size_t)8, SFE_UBLOX_MGA_ASSIST_ACK_NO, 0); + + // Check pushResult == 8 + if (pushResult != 8) + { +#ifndef SFE_UBLOX_REDUCED_PROG_MEM + if (_printDebug == true) + { + _debugSerial->println(F("readNavigationDatabase: pushAssistNowDataInternal failed!")); + } +#endif + i2cPollingWait = currentI2cPollingWait; // Restore i2cPollingWait + setAckAiding(currentAckAiding); // Restore Ack Aiding + return ((size_t)0); + } + + // Now keep checking for the arrival of UBX-MGA-DBD packets and write them to dataBytes + bool keepGoing = true; + unsigned long startTime = millis(); + uint32_t databaseEntriesRX = 0; // Keep track of how many database entries are received + size_t numBytesReceived = 0; // Keep track of how many bytes are received + + while (keepGoing && (millis() < (startTime + maxWait))) + { + checkUblox(); + + while (packetUBXMGADBD->head != packetUBXMGADBD->tail) // Does the MGA DBD ringbuffer contain any data? + { + // The data will be valid - process will have already checked it. So we can simply copy the data into dataBuffer. + // We do not need to check if there is room to store the entire database entry. pushAssistNowData will check the data before pushing it. + if (numBytesReceived < maxNumDataBytes) + *(dataBytes + (numBytesReceived++)) = packetUBXMGADBD->data[packetUBXMGADBD->tail].dbdEntryHeader1; + if (numBytesReceived < maxNumDataBytes) + *(dataBytes + (numBytesReceived++)) = packetUBXMGADBD->data[packetUBXMGADBD->tail].dbdEntryHeader2; + if (numBytesReceived < maxNumDataBytes) + *(dataBytes + (numBytesReceived++)) = packetUBXMGADBD->data[packetUBXMGADBD->tail].dbdEntryClass; + if (numBytesReceived < maxNumDataBytes) + *(dataBytes + (numBytesReceived++)) = packetUBXMGADBD->data[packetUBXMGADBD->tail].dbdEntryID; + if (numBytesReceived < maxNumDataBytes) + *(dataBytes + (numBytesReceived++)) = packetUBXMGADBD->data[packetUBXMGADBD->tail].dbdEntryLenLSB; + if (numBytesReceived < maxNumDataBytes) + *(dataBytes + (numBytesReceived++)) = packetUBXMGADBD->data[packetUBXMGADBD->tail].dbdEntryLenMSB; + size_t msgLen = (((size_t)packetUBXMGADBD->data[packetUBXMGADBD->tail].dbdEntryLenMSB) * 256) + ((size_t)packetUBXMGADBD->data[packetUBXMGADBD->tail].dbdEntryLenLSB); + for (size_t i = 0; i < msgLen; i++) + { + if (numBytesReceived < maxNumDataBytes) + *(dataBytes + (numBytesReceived++)) = packetUBXMGADBD->data[packetUBXMGADBD->tail].dbdEntry[i]; + } + if (numBytesReceived < maxNumDataBytes) + *(dataBytes + (numBytesReceived++)) = packetUBXMGADBD->data[packetUBXMGADBD->tail].dbdEntryChecksumA; + if (numBytesReceived < maxNumDataBytes) + *(dataBytes + (numBytesReceived++)) = packetUBXMGADBD->data[packetUBXMGADBD->tail].dbdEntryChecksumB; + + // Increment the tail + packetUBXMGADBD->tail++; + if (packetUBXMGADBD->tail == UBX_MGA_DBD_RINGBUFFER_LEN) + packetUBXMGADBD->tail = 0; + + databaseEntriesRX++; // Increment the number of entries received + } + + // The final MGA-ACK is sent at the end of the DBD packets. So, we need to check the ACK buffer _after_ the DBD buffer. + while (packetUBXMGAACK->head != packetUBXMGAACK->tail) // Does the MGA ACK ringbuffer contain any data? + { + // Check if we've received the correct ACK + bool dataAckd = true; + dataAckd &= (packetUBXMGAACK->data[packetUBXMGAACK->tail].msgId == UBX_MGA_DBD); // Check if the message ID matches + dataAckd &= (packetUBXMGAACK->data[packetUBXMGAACK->tail].msgPayloadStart[0] == (uint8_t)(databaseEntriesRX & 0xFF)); // Check if the ACK contents match databaseEntriesRX + dataAckd &= (packetUBXMGAACK->data[packetUBXMGAACK->tail].msgPayloadStart[1] == (uint8_t)((databaseEntriesRX >> 8) & 0xFF)); + dataAckd &= (packetUBXMGAACK->data[packetUBXMGAACK->tail].msgPayloadStart[2] == (uint8_t)((databaseEntriesRX >> 16) & 0xFF)); + dataAckd &= (packetUBXMGAACK->data[packetUBXMGAACK->tail].msgPayloadStart[3] == (uint8_t)((databaseEntriesRX >> 24) & 0xFF)); + + if (dataAckd) // Is the ACK valid? + { +#ifndef SFE_UBLOX_REDUCED_PROG_MEM + if ((_printDebug == true) || (_printLimitedDebug == true)) // This is important. Print this if doing limited debugging + { + _debugSerial->print(F("readNavigationDatabase: ACK received. databaseEntriesRX is ")); + _debugSerial->print(databaseEntriesRX); + _debugSerial->print(F(". numBytesReceived is ")); + _debugSerial->print(numBytesReceived); + _debugSerial->print(F(". DBD read complete after ")); + _debugSerial->print(millis() - startTime); + _debugSerial->println(F(" ms")); + } +#endif + keepGoing = false; + } + // Increment the tail + packetUBXMGAACK->tail++; + if (packetUBXMGAACK->tail == UBX_MGA_ACK_DATA0_RINGBUFFER_LEN) + packetUBXMGAACK->tail = 0; + } + } + + if (keepGoing) // If keepGoing is still true, we must have timed out + { + if ((_printDebug == true) || (_printLimitedDebug == true)) // This is important. Print this if doing limited debugging + { + _debugSerial->println(F("readNavigationDatabase: DBD RX timed out!")); + } + } + + i2cPollingWait = currentI2cPollingWait; // Restore i2cPollingWait + setAckAiding(currentAckAiding); // Restore Ack Aiding + + return (numBytesReceived); +} + +// PRIVATE: Allocate RAM for packetUBXMGADBD and initialize it +bool SFE_UBLOX_GNSS::initPacketUBXMGADBD() +{ + packetUBXMGADBD = new UBX_MGA_DBD_t; //Allocate RAM for the main struct + if (packetUBXMGADBD == NULL) + { + if ((_printDebug == true) || (_printLimitedDebug == true)) // This is important. Print this if doing limited debugging + _debugSerial->println(F("initPacketUBXMGADBD: RAM alloc failed!")); + return (false); + } + packetUBXMGADBD->head = 0; // Initialize the ring buffer pointers + packetUBXMGADBD->tail = 0; + return (true); +} + // Support for data logging //Set the file buffer size. This must be called _before_ .begin @@ -4103,7 +5125,7 @@ bool SFE_UBLOX_GNSS::storePacket(ubxPacket *msg) } //Store the two sync chars - uint8_t sync_chars[] = {0xB5, 0x62}; + uint8_t sync_chars[] = {UBX_SYNCH_1, UBX_SYNCH_2}; writeToFileBuffer(sync_chars, 2); //Store the Class & ID @@ -5383,6 +6405,86 @@ bool SFE_UBLOX_GNSS::setTimePulseParameters(UBX_CFG_TP5_data_t *data, uint16_t m return (sendCommand(&packetCfg, maxWait) == SFE_UBLOX_STATUS_DATA_SENT); // We are only expecting an ACK } +//UBX-CFG-NAVX5 - get/set the ackAiding byte. If ackAiding is 1, UBX-MGA-ACK messages will be sent by the module to acknowledge the MGA data +uint8_t SFE_UBLOX_GNSS::getAckAiding(uint16_t maxWait) // Get the ackAiding byte - returns 255 if the sendCommand fails +{ + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_NAVX5; + packetCfg.len = 0; + packetCfg.startingSpot = 0; + + if (sendCommand(&packetCfg, maxWait) != SFE_UBLOX_STATUS_DATA_RECEIVED) // We are expecting data and an ACK + return (255); + + // Extract the ackAiding byte + // There are three versions of UBX-CFG-NAVX5 but ackAiding is always in byte 17 + return (extractByte(&packetCfg, 17)); +} +bool SFE_UBLOX_GNSS::setAckAiding(uint8_t ackAiding, uint16_t maxWait) // Set the ackAiding byte +{ + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_NAVX5; + packetCfg.len = 0; + packetCfg.startingSpot = 0; + + if (sendCommand(&packetCfg, maxWait) != SFE_UBLOX_STATUS_DATA_RECEIVED) // We are expecting data and an ACK + return (false); + + // Set the ackAiding byte + // There are three versions of UBX-CFG-NAVX5 but ackAiding is always in byte 17 + payloadCfg[17] = ackAiding; + + // There are three versions of UBX-CFG-NAVX5 but the ackAid flag is always in bit 10 of mask1 + payloadCfg[2] = 0x00; // Clear the LS byte of mask1 + payloadCfg[3] = 0x04; // Set _only_ the ackAid flag = bit 10 of mask1 = bit 2 of the MS byte + payloadCfg[4] = 0x00; // Clear the LS byte of mask2, just in case + payloadCfg[5] = 0x00; // Clear the LS byte of mask2, just in case + + return (sendCommand(&packetCfg, maxWait) == SFE_UBLOX_STATUS_DATA_SENT); // We are only expecting an ACK +} + +//AssistNow Autonomous support +//UBX-CFG-NAVX5 - get the AssistNow Autonomous configuration (aopCfg) - returns 255 if the sendCommand fails +uint8_t SFE_UBLOX_GNSS::getAopCfg(uint16_t maxWait) +{ + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_NAVX5; + packetCfg.len = 0; + packetCfg.startingSpot = 0; + + if (sendCommand(&packetCfg, maxWait) != SFE_UBLOX_STATUS_DATA_RECEIVED) // We are expecting data and an ACK + return (255); + + // Extract the aopCfg byte + // There are three versions of UBX-CFG-NAVX5 but aopCfg is always in byte 27 + return (extractByte(&packetCfg, 27)); +} +// Set the aopCfg byte and the aopOrdMaxErr word +bool SFE_UBLOX_GNSS::setAopCfg(uint8_t aopCfg, uint16_t aopOrbMaxErr, uint16_t maxWait) +{ + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_NAVX5; + packetCfg.len = 0; + packetCfg.startingSpot = 0; + + if (sendCommand(&packetCfg, maxWait) != SFE_UBLOX_STATUS_DATA_RECEIVED) // We are expecting data and an ACK + return (false); + + // Set the aopCfg byte + // There are three versions of UBX-CFG-NAVX5 but aopCfg is always in byte 27 and aopOrbMaxErr is always in bytes 30 & 31 + payloadCfg[27] = aopCfg; + payloadCfg[30] = (uint8_t)(aopOrbMaxErr & 0xFF); // aopOrbMaxErr LSB + payloadCfg[31] = (uint8_t)(aopOrbMaxErr >> 8); // aopOrbMaxErr MSB + + // There are three versions of UBX-CFG-NAVX5 but the aop flag is always in bit 14 of mask1 + payloadCfg[2] = 0x00; // Clear the LS byte of mask1 + payloadCfg[3] = 0x40; // Set _only_ the aop flag = bit 14 of mask1 = bit 6 of the MS byte + payloadCfg[4] = 0x00; // Clear the LS byte of mask2, just in case + payloadCfg[5] = 0x00; // Clear the LS byte of mask2, just in case + + return (sendCommand(&packetCfg, maxWait) == SFE_UBLOX_STATUS_DATA_SENT); // We are only expecting an ACK +} + // CONFIGURATION INTERFACE (protocol v27 and above) //Form 32-bit key from group/id/size @@ -6439,11 +7541,11 @@ bool SFE_UBLOX_GNSS::initPacketUBXNAVATT() return (true); } -//Mark all the DOP data as read/stale. This is handy to get data alignment after CRC failure +//Mark all the ATT data as read/stale. This is handy to get data alignment after CRC failure void SFE_UBLOX_GNSS::flushNAVATT() { if (packetUBXNAVATT == NULL) return; // Bail if RAM has not been allocated (otherwise we could be writing anywhere!) - packetUBXNAVATT->moduleQueried.moduleQueried.all = 0; //Mark all DOPs as stale (read before) + packetUBXNAVATT->moduleQueried.moduleQueried.all = 0; //Mark all ATT data as stale (read before) } //Log this data in file buffer @@ -7686,6 +8788,164 @@ bool SFE_UBLOX_GNSS::initPacketUBXNAVSVIN() return (true); } +// ***** NAV SAT automatic support + +//Signal information +//Returns true if commands was successful +bool SFE_UBLOX_GNSS::getNAVSAT(uint16_t maxWait) +{ + if (packetUBXNAVSAT == NULL) initPacketUBXNAVSAT(); //Check that RAM has been allocated for the NAVSAT data + if (packetUBXNAVSAT == NULL) //Bail if the RAM allocation failed + return (false); + + if (packetUBXNAVSAT->automaticFlags.flags.bits.automatic && packetUBXNAVSAT->automaticFlags.flags.bits.implicitUpdate) + { + //The GPS is automatically reporting, we just check whether we got unread data + checkUbloxInternal(&packetCfg, UBX_CLASS_NAV, UBX_NAV_SAT); + return packetUBXNAVSAT->moduleQueried; + } + else if (packetUBXNAVSAT->automaticFlags.flags.bits.automatic && !packetUBXNAVSAT->automaticFlags.flags.bits.implicitUpdate) + { + //Someone else has to call checkUblox for us... + return (false); + } + else + { + //The GPS is not automatically reporting NAVSAT so we have to poll explicitly + packetCfg.cls = UBX_CLASS_NAV; + packetCfg.id = UBX_NAV_SAT; + packetCfg.len = 0; + packetCfg.startingSpot = 0; + + //The data is parsed as part of processing the response + sfe_ublox_status_e retVal = sendCommand(&packetCfg, maxWait); + + if (retVal == SFE_UBLOX_STATUS_DATA_RECEIVED) + return (true); + + if (retVal == SFE_UBLOX_STATUS_DATA_OVERWRITTEN) + { + return (true); + } + + return (false); + } +} + +//Enable or disable automatic NAVSAT message generation by the GNSS. This changes the way getNAVSAT +//works. +bool SFE_UBLOX_GNSS::setAutoNAVSAT(bool enable, uint16_t maxWait) +{ + return setAutoNAVSATrate(enable ? 1 : 0, true, maxWait); +} + +//Enable or disable automatic NAVSAT message generation by the GNSS. This changes the way getNAVSAT +//works. +bool SFE_UBLOX_GNSS::setAutoNAVSAT(bool enable, bool implicitUpdate, uint16_t maxWait) +{ + return setAutoNAVSATrate(enable ? 1 : 0, implicitUpdate, maxWait); +} + +//Enable or disable automatic HNR attitude message generation by the GNSS. This changes the way getNAVSAT +//works. +bool SFE_UBLOX_GNSS::setAutoNAVSATrate(uint8_t rate, bool implicitUpdate, uint16_t maxWait) +{ + if (packetUBXNAVSAT == NULL) initPacketUBXNAVSAT(); //Check that RAM has been allocated for the data + if (packetUBXNAVSAT == NULL) //Only attempt this if RAM allocation was successful + return false; + + if (rate > 127) rate = 127; + + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_MSG; + packetCfg.len = 3; + packetCfg.startingSpot = 0; + payloadCfg[0] = UBX_CLASS_NAV; + payloadCfg[1] = UBX_NAV_SAT; + payloadCfg[2] = rate; // rate relative to navigation freq. + + bool ok = ((sendCommand(&packetCfg, maxWait)) == SFE_UBLOX_STATUS_DATA_SENT); // We are only expecting an ACK + if (ok) + { + packetUBXNAVSAT->automaticFlags.flags.bits.automatic = (rate > 0); + packetUBXNAVSAT->automaticFlags.flags.bits.implicitUpdate = implicitUpdate; + } + packetUBXNAVSAT->moduleQueried = false; // Mark data as stale + return ok; +} + +//Enable automatic navigation message generation by the GNSS. +bool SFE_UBLOX_GNSS::setAutoNAVSATcallback(void (*callbackPointer)(UBX_NAV_SAT_data_t), uint16_t maxWait) +{ + // Enable auto messages. Set implicitUpdate to false as we expect the user to call checkUblox manually. + bool result = setAutoNAVSAT(true, false, maxWait); + if (!result) + return (result); // Bail if setAuto failed + + if (packetUBXNAVSAT->callbackData == NULL) //Check if RAM has been allocated for the callback copy + { + packetUBXNAVSAT->callbackData = new UBX_NAV_SAT_data_t; //Allocate RAM for the main struct + } + + if (packetUBXNAVSAT->callbackData == NULL) + { + if ((_printDebug == true) || (_printLimitedDebug == true)) // This is important. Print this if doing limited debugging + _debugSerial->println(F("setAutoNAVSATcallback: RAM alloc failed!")); + return (false); + } + + packetUBXNAVSAT->callbackPointer = callbackPointer; + return (true); +} + +//In case no config access to the GNSS is possible and HNR attitude is send cyclically already +//set config to suitable parameters +bool SFE_UBLOX_GNSS::assumeAutoNAVSAT(bool enabled, bool implicitUpdate) +{ + if (packetUBXNAVSAT == NULL) initPacketUBXNAVSAT(); //Check that RAM has been allocated for the NAVSAT data + if (packetUBXNAVSAT == NULL) //Bail if the RAM allocation failed + return (false); + + bool changes = packetUBXNAVSAT->automaticFlags.flags.bits.automatic != enabled || packetUBXNAVSAT->automaticFlags.flags.bits.implicitUpdate != implicitUpdate; + if (changes) + { + packetUBXNAVSAT->automaticFlags.flags.bits.automatic = enabled; + packetUBXNAVSAT->automaticFlags.flags.bits.implicitUpdate = implicitUpdate; + } + return changes; +} + +// PRIVATE: Allocate RAM for packetUBXNAVSAT and initialize it +bool SFE_UBLOX_GNSS::initPacketUBXNAVSAT() +{ + packetUBXNAVSAT = new UBX_NAV_SAT_t ; //Allocate RAM for the main struct + if (packetUBXNAVSAT == NULL) + { + if ((_printDebug == true) || (_printLimitedDebug == true)) // This is important. Print this if doing limited debugging + _debugSerial->println(F("initPacketUBXNAVSAT: RAM alloc failed!")); + return (false); + } + packetUBXNAVSAT->automaticFlags.flags.all = 0; + packetUBXNAVSAT->callbackPointer = NULL; + packetUBXNAVSAT->callbackData = NULL; + packetUBXNAVSAT->moduleQueried = false; + return (true); +} + +//Mark all the data as read/stale +void SFE_UBLOX_GNSS::flushNAVSAT() +{ + if (packetUBXNAVSAT == NULL) return; // Bail if RAM has not been allocated (otherwise we could be writing anywhere!) + packetUBXNAVSAT->moduleQueried = false; //Mark all datums as stale (read before) +} + +//Log this data in file buffer +void SFE_UBLOX_GNSS::logNAVSAT(bool enabled) +{ + if (packetUBXNAVSAT == NULL) return; // Bail if RAM has not been allocated (otherwise we could be writing anywhere!) + packetUBXNAVSAT->automaticFlags.flags.bits.addToFileBuffer = (uint8_t)enabled; +} + // ***** NAV RELPOSNED automatic support //Relative Positioning Information in NED frame @@ -7847,6 +9107,182 @@ void SFE_UBLOX_GNSS::logNAVRELPOSNED(bool enabled) packetUBXNAVRELPOSNED->automaticFlags.flags.bits.addToFileBuffer = (uint8_t)enabled; } +// ***** AOPSTATUS automatic support + +bool SFE_UBLOX_GNSS::getAOPSTATUS(uint16_t maxWait) +{ + if (packetUBXNAVAOPSTATUS == NULL) initPacketUBXNAVAOPSTATUS(); //Check that RAM has been allocated for the AOPSTATUS data + if (packetUBXNAVAOPSTATUS == NULL) //Bail if the RAM allocation failed + return (false); + + if (packetUBXNAVAOPSTATUS->automaticFlags.flags.bits.automatic && packetUBXNAVAOPSTATUS->automaticFlags.flags.bits.implicitUpdate) + { + //The GPS is automatically reporting, we just check whether we got unread data + // if (_printDebug == true) + // { + // _debugSerial->println(F("getAOPSTATUS: Autoreporting")); + // } + checkUbloxInternal(&packetCfg, UBX_CLASS_NAV, UBX_NAV_AOPSTATUS); + return packetUBXNAVAOPSTATUS->moduleQueried.moduleQueried.bits.all; + } + else if (packetUBXNAVAOPSTATUS->automaticFlags.flags.bits.automatic && !packetUBXNAVAOPSTATUS->automaticFlags.flags.bits.implicitUpdate) + { + //Someone else has to call checkUblox for us... + // if (_printDebug == true) + // { + // _debugSerial->println(F("getAOPSTATUS: Exit immediately")); + // } + return (false); + } + else + { + // if (_printDebug == true) + // { + // _debugSerial->println(F("getAOPSTATUS: Polling")); + // } + + //The GPS is not automatically reporting navigation position so we have to poll explicitly + packetCfg.cls = UBX_CLASS_NAV; + packetCfg.id = UBX_NAV_AOPSTATUS; + packetCfg.len = 0; + packetCfg.startingSpot = 0; + + //The data is parsed as part of processing the response + sfe_ublox_status_e retVal = sendCommand(&packetCfg, maxWait); + + if (retVal == SFE_UBLOX_STATUS_DATA_RECEIVED) + return (true); + + if (retVal == SFE_UBLOX_STATUS_DATA_OVERWRITTEN) + { + // if (_printDebug == true) + // { + // _debugSerial->println(F("getAOPSTATUS: data in packetCfg was OVERWRITTEN by another message (but that's OK)")); + // } + return (true); + } + + // if (_printDebug == true) + // { + // _debugSerial->print(F("getAOPSTATUS retVal: ")); + // _debugSerial->println(statusString(retVal)); + // } + return (false); + } +} + +//Enable or disable automatic navigation message generation by the GNSS. This changes the way getAOPSTATUS +//works. +bool SFE_UBLOX_GNSS::setAutoAOPSTATUS(bool enable, uint16_t maxWait) +{ + return setAutoAOPSTATUSrate(enable ? 1 : 0, true, maxWait); +} + +//Enable or disable automatic navigation message generation by the GNSS. This changes the way getAOPSTATUS +//works. +bool SFE_UBLOX_GNSS::setAutoAOPSTATUS(bool enable, bool implicitUpdate, uint16_t maxWait) +{ + return setAutoAOPSTATUSrate(enable ? 1 : 0, implicitUpdate, maxWait); +} + +//Enable or disable automatic navigation message generation by the GNSS. This changes the way getAOPSTATUS +//works. +bool SFE_UBLOX_GNSS::setAutoAOPSTATUSrate(uint8_t rate, bool implicitUpdate, uint16_t maxWait) +{ + if (packetUBXNAVAOPSTATUS == NULL) initPacketUBXNAVAOPSTATUS(); //Check that RAM has been allocated for the data + if (packetUBXNAVAOPSTATUS == NULL) //Only attempt this if RAM allocation was successful + return false; + + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_MSG; + packetCfg.len = 3; + packetCfg.startingSpot = 0; + payloadCfg[0] = UBX_CLASS_NAV; + payloadCfg[1] = UBX_NAV_AOPSTATUS; + payloadCfg[2] = rate; // rate relative to navigation freq. + + bool ok = ((sendCommand(&packetCfg, maxWait)) == SFE_UBLOX_STATUS_DATA_SENT); // We are only expecting an ACK + if (ok) + { + packetUBXNAVAOPSTATUS->automaticFlags.flags.bits.automatic = (rate > 0); + packetUBXNAVAOPSTATUS->automaticFlags.flags.bits.implicitUpdate = implicitUpdate; + } + packetUBXNAVAOPSTATUS->moduleQueried.moduleQueried.bits.all = false; + return ok; +} + +//Enable automatic navigation message generation by the GNSS. +bool SFE_UBLOX_GNSS::setAutoAOPSTATUScallback(void (*callbackPointer)(UBX_NAV_AOPSTATUS_data_t), uint16_t maxWait) +{ + // Enable auto messages. Set implicitUpdate to false as we expect the user to call checkUblox manually. + bool result = setAutoAOPSTATUS(true, false, maxWait); + if (!result) + return (result); // Bail if setAuto failed + + if (packetUBXNAVAOPSTATUS->callbackData == NULL) //Check if RAM has been allocated for the callback copy + { + packetUBXNAVAOPSTATUS->callbackData = new UBX_NAV_AOPSTATUS_data_t; //Allocate RAM for the main struct + } + + if (packetUBXNAVAOPSTATUS->callbackData == NULL) + { + if ((_printDebug == true) || (_printLimitedDebug == true)) // This is important. Print this if doing limited debugging + _debugSerial->println(F("setAutoAOPSTATUScallback: RAM alloc failed!")); + return (false); + } + + packetUBXNAVAOPSTATUS->callbackPointer = callbackPointer; + return (true); +} + +//In case no config access to the GNSS is possible and AOPSTATUS is send cyclically already +//set config to suitable parameters +bool SFE_UBLOX_GNSS::assumeAutoAOPSTATUS(bool enabled, bool implicitUpdate) +{ + if (packetUBXNAVAOPSTATUS == NULL) initPacketUBXNAVAOPSTATUS(); //Check that RAM has been allocated for the data + if (packetUBXNAVAOPSTATUS == NULL) //Only attempt this if RAM allocation was successful + return false; + + bool changes = packetUBXNAVAOPSTATUS->automaticFlags.flags.bits.automatic != enabled || packetUBXNAVAOPSTATUS->automaticFlags.flags.bits.implicitUpdate != implicitUpdate; + if (changes) + { + packetUBXNAVAOPSTATUS->automaticFlags.flags.bits.automatic = enabled; + packetUBXNAVAOPSTATUS->automaticFlags.flags.bits.implicitUpdate = implicitUpdate; + } + return changes; +} + +// PRIVATE: Allocate RAM for packetUBXNAVAOPSTATUS and initialize it +bool SFE_UBLOX_GNSS::initPacketUBXNAVAOPSTATUS() +{ + packetUBXNAVAOPSTATUS = new UBX_NAV_AOPSTATUS_t; //Allocate RAM for the main struct + if (packetUBXNAVAOPSTATUS == NULL) + { + if ((_printDebug == true) || (_printLimitedDebug == true)) // This is important. Print this if doing limited debugging + _debugSerial->println(F("initPacketUBXNAVAOPSTATUS: RAM alloc failed!")); + return (false); + } + packetUBXNAVAOPSTATUS->automaticFlags.flags.all = 0; + packetUBXNAVAOPSTATUS->callbackPointer = NULL; + packetUBXNAVAOPSTATUS->callbackData = NULL; + packetUBXNAVAOPSTATUS->moduleQueried.moduleQueried.all = 0; + return (true); +} + +//Mark all the AOPSTATUS data as read/stale. This is handy to get data alignment after CRC failure +void SFE_UBLOX_GNSS::flushAOPSTATUS() +{ + if (packetUBXNAVAOPSTATUS == NULL) return; // Bail if RAM has not been allocated (otherwise we could be writing anywhere!) + packetUBXNAVAOPSTATUS->moduleQueried.moduleQueried.all = 0; //Mark all AOPSTATUSs as stale (read before) +} + +//Log this data in file buffer +void SFE_UBLOX_GNSS::logAOPSTATUS(bool enabled) +{ + if (packetUBXNAVAOPSTATUS == NULL) return; // Bail if RAM has not been allocated (otherwise we could be writing anywhere!) + packetUBXNAVAOPSTATUS->automaticFlags.flags.bits.addToFileBuffer = (uint8_t)enabled; +} + // ***** RXM SFRBX automatic support bool SFE_UBLOX_GNSS::getRXMSFRBX(uint16_t maxWait) @@ -9884,12 +11320,15 @@ uint32_t SFE_UBLOX_GNSS::getProcessNMEAMask() //Max is 40Hz(?!) bool SFE_UBLOX_GNSS::setNavigationFrequency(uint8_t navFreq, uint16_t maxWait) { + if (navFreq == 0) // Return now if navFreq is zero + return (false); + if (navFreq > 40) navFreq = 40; // Limit navFreq to 40Hz so i2cPollingWait is set correctly //Adjust the I2C polling timeout based on update rate //Do this even if the sendCommand fails - i2cPollingWaitNAV = 1000 / (((int)navFreq) * 4); //This is the number of ms to wait between checks for new I2C data + i2cPollingWaitNAV = 1000 / (((int)navFreq) * 4); //This is the number of ms to wait between checks for new I2C data. Max is 250. Min is 6. i2cPollingWait = i2cPollingWaitNAV < i2cPollingWaitHNR ? i2cPollingWaitNAV : i2cPollingWaitHNR; // Set i2cPollingWait to the lower of NAV and HNR //Query the module @@ -9940,7 +11379,10 @@ bool SFE_UBLOX_GNSS::setMeasurementRate(uint16_t rate, uint16_t maxWait) rate = 25; //Adjust the I2C polling timeout based on update rate - i2cPollingWaitNAV = rate / 4; //This is the number of ms to wait between checks for new I2C data + if (rate >= 1000) + i2cPollingWaitNAV = 250; + else + i2cPollingWaitNAV = rate / 4; //This is the number of ms to wait between checks for new I2C data i2cPollingWait = i2cPollingWaitNAV < i2cPollingWaitHNR ? i2cPollingWaitNAV : i2cPollingWaitHNR; // Set i2cPollingWait to the lower of NAV and HNR //Query the module @@ -11113,6 +12555,34 @@ float SFE_UBLOX_GNSS::getRelPosAccD(uint16_t maxWait) // Returned as m return (((float)packetUBXNAVRELPOSNED->data.accD) / 10000.0); // Convert to m } +// ***** AOPSTATUS Helper Functions + +uint8_t SFE_UBLOX_GNSS::getAOPSTATUSuseAOP(uint16_t maxWait) +{ + if (packetUBXNAVAOPSTATUS == NULL) initPacketUBXNAVAOPSTATUS(); //Check that RAM has been allocated for the AOPSTATUS data + if (packetUBXNAVAOPSTATUS == NULL) //Bail if the RAM allocation failed + return 0; + + if (packetUBXNAVAOPSTATUS->moduleQueried.moduleQueried.bits.useAOP == false) + getAOPSTATUS(maxWait); + packetUBXNAVAOPSTATUS->moduleQueried.moduleQueried.bits.useAOP = false; //Since we are about to give this to user, mark this data as stale + packetUBXNAVAOPSTATUS->moduleQueried.moduleQueried.bits.all = false; + return (packetUBXNAVAOPSTATUS->data.aopCfg.bits.useAOP); +} + +uint8_t SFE_UBLOX_GNSS::getAOPSTATUSstatus(uint16_t maxWait) +{ + if (packetUBXNAVAOPSTATUS == NULL) initPacketUBXNAVAOPSTATUS(); //Check that RAM has been allocated for the AOPSTATUS data + if (packetUBXNAVAOPSTATUS == NULL) //Bail if the RAM allocation failed + return 0; + + if (packetUBXNAVAOPSTATUS->moduleQueried.moduleQueried.bits.status == false) + getAOPSTATUS(maxWait); + packetUBXNAVAOPSTATUS->moduleQueried.moduleQueried.bits.status = false; //Since we are about to give this to user, mark this data as stale + packetUBXNAVAOPSTATUS->moduleQueried.moduleQueried.bits.all = false; + return (packetUBXNAVAOPSTATUS->data.status); +} + // ***** ESF Helper Functions float SFE_UBLOX_GNSS::getESFroll(uint16_t maxWait) // Returned as degrees @@ -11228,12 +12698,15 @@ bool SFE_UBLOX_GNSS::getSensorFusionStatus(UBX_ESF_STATUS_sensorStatus_t *sensor // Returns true if the setHNRNavigationRate is successful bool SFE_UBLOX_GNSS::setHNRNavigationRate(uint8_t rate, uint16_t maxWait) { + if (rate == 0) // Return now if rate is zero + return (false); + if (rate > 40) rate = 40; // Limit rate to 40Hz so i2cPollingWait is set correctly //Adjust the I2C polling timeout based on update rate //Do this even if the sendCommand is not ACK'd - i2cPollingWaitHNR = 1000 / (((int)rate) * 4); //This is the number of ms to wait between checks for new I2C data + i2cPollingWaitHNR = 1000 / (((int)rate) * 4); //This is the number of ms to wait between checks for new I2C data. Max 250. Min 6. i2cPollingWait = i2cPollingWaitNAV < i2cPollingWaitHNR ? i2cPollingWaitNAV : i2cPollingWaitHNR; // Set i2cPollingWait to the lower of NAV and HNR packetCfg.cls = UBX_CLASS_CFG; diff --git a/src/SparkFun_u-blox_GNSS_Arduino_Library.h b/src/SparkFun_u-blox_GNSS_Arduino_Library.h index 1bc37b9..dcbe999 100644 --- a/src/SparkFun_u-blox_GNSS_Arduino_Library.h +++ b/src/SparkFun_u-blox_GNSS_Arduino_Library.h @@ -200,7 +200,7 @@ const uint8_t UBX_CFG_BATCH = 0x93; //Get/set data batching configuration. const uint8_t UBX_CFG_CFG = 0x09; //Clear, Save, and Load Configurations. Used to save current configuration const uint8_t UBX_CFG_DAT = 0x06; //Set User-defined Datum or The currently defined Datum const uint8_t UBX_CFG_DGNSS = 0x70; //DGNSS configuration -const uint8_t UBX_CFG_ESFALG = 0x56; //ESF alignment +const uint8_t UBX_CFG_ESFALG = 0x56; //ESF alignment const uint8_t UBX_CFG_ESFA = 0x4C; //ESF accelerometer const uint8_t UBX_CFG_ESFG = 0x4D; //ESF gyro const uint8_t UBX_CFG_GEOFENCE = 0x69; //Geofencing configuration. Used to configure a geofence @@ -295,6 +295,7 @@ const uint8_t UBX_LOG_STRING = 0x04; //Store arbitrary string on on-board fl //Class: MGA //The following are used to configure MGA UBX messages (Multiple GNSS Assistance Messages). Descriptions from UBX messages overview (ZED_F9P Interface Description Document page 34) const uint8_t UBX_MGA_ACK_DATA0 = 0x60; //Multiple GNSS Acknowledge message +const uint8_t UBX_MGA_ANO = 0x20; //Multiple GNSS AssistNow Offline assistance - NOT SUPPORTED BY THE ZED-F9P! "The ZED-F9P supports AssistNow Online only." const uint8_t UBX_MGA_BDS_EPH = 0x03; //BDS Ephemeris Assistance const uint8_t UBX_MGA_BDS_ALM = 0x03; //BDS Almanac Assistance const uint8_t UBX_MGA_BDS_HEALTH = 0x03; //BDS Health Assistance @@ -368,6 +369,7 @@ const uint8_t UBX_NAV_TIMELS = 0x26; //Leap second event information const uint8_t UBX_NAV_TIMEUTC = 0x21; //UTC Time Solution const uint8_t UBX_NAV_VELECEF = 0x11; //Velocity Solution in ECEF const uint8_t UBX_NAV_VELNED = 0x12; //Velocity Solution in NED +const uint8_t UBX_NAV_AOPSTATUS = 0x60; //AssistNow Autonomous status //Class: RXM //The following are used to configure the RXM UBX messages (receiver manager messages). Descriptions from UBX messages overview (ZED_F9P Interface Description Document page 36) @@ -496,6 +498,27 @@ enum sfe_ublox_ls_src_e SFE_UBLOX_LS_SRC_UNKNOWN = 255 }; +typedef enum +{ + SFE_UBLOX_MGA_ASSIST_ACK_NO, // Do not expect UBX-MGA-ACK's. If the module outputs them, they will be ignored + SFE_UBLOX_MGA_ASSIST_ACK_YES, // Expect and check for UBX-MGA-ACK's + SFE_UBLOX_MGA_ASSIST_ACK_ENQUIRE // Check UBX-CFG-NAVX5 ackAiding to determine if UBX-MGA-ACK's are expected +} sfe_ublox_mga_assist_ack_e; + +// The infoCode byte included in UBX-MGA-ACK-DATA0 +enum sfe_ublox_mga_ack_infocode_e +{ + SFE_UBLOX_MGA_ACK_INFOCODE_ACCEPTED, + SFE_UBLOX_MGA_ACK_INFOCODE_NO_TIME, + SFE_UBLOX_MGA_ACK_INFOCODE_NOT_SUPPORTED, + SFE_UBLOX_MGA_ACK_INFOCODE_SIZE_MISMATCH, + SFE_UBLOX_MGA_ACK_INFOCODE_NOT_STORED, + SFE_UBLOX_MGA_ACK_INFOCODE_NOT_READY, + SFE_UBLOX_MGA_ACK_INFOCODE_TYPE_UNKNOWN +}; + +//-=-=-=-=- + #ifndef MAX_PAYLOAD_SIZE // v2.0: keep this for backwards-compatibility, but this is largely superseded by setPacketCfgPayloadSize #define MAX_PAYLOAD_SIZE 256 //We need ~220 bytes for getProtocolVersion on most ublox modules @@ -683,6 +706,50 @@ class SFE_UBLOX_GNSS // Default to using a restart between transmissions. But processors like ESP32 seem to need a stop (#30). Set stop to true to use a stop instead. bool pushRawData(uint8_t *dataBytes, size_t numDataBytes, bool stop = false); + // Push MGA AssistNow data to the module. + // Check for UBX-MGA-ACK responses if required (if mgaAck is YES or ENQUIRE). + // Wait for maxWait millis after sending each packet (if mgaAck is NO). + // Return how many bytes were pushed successfully. + // If skipTime is true, any UBX-MGA-INI-TIME_UTC or UBX-MGA-INI-TIME_GNSS packets found in the data will be skipped, + // allowing the user to override with their own time data with setUTCTimeAssistance. + // offset allows a sub-set of the data to be sent - starting from offset. + #define defaultMGAdelay 7 // Default to waiting for 7ms between each MGA message + size_t pushAssistNowData(const String &dataBytes, size_t numDataBytes, sfe_ublox_mga_assist_ack_e mgaAck = SFE_UBLOX_MGA_ASSIST_ACK_NO, uint16_t maxWait = defaultMGAdelay); + size_t pushAssistNowData(const uint8_t *dataBytes, size_t numDataBytes, sfe_ublox_mga_assist_ack_e mgaAck = SFE_UBLOX_MGA_ASSIST_ACK_NO, uint16_t maxWait = defaultMGAdelay); + size_t pushAssistNowData(bool skipTime, const String &dataBytes, size_t numDataBytes, sfe_ublox_mga_assist_ack_e mgaAck = SFE_UBLOX_MGA_ASSIST_ACK_NO, uint16_t maxWait = defaultMGAdelay); + size_t pushAssistNowData(bool skipTime, const uint8_t *dataBytes, size_t numDataBytes, sfe_ublox_mga_assist_ack_e mgaAck = SFE_UBLOX_MGA_ASSIST_ACK_NO, uint16_t maxWait = defaultMGAdelay); + size_t pushAssistNowData(size_t offset, bool skipTime, const String &dataBytes, size_t numDataBytes, sfe_ublox_mga_assist_ack_e mgaAck = SFE_UBLOX_MGA_ASSIST_ACK_NO, uint16_t maxWait = defaultMGAdelay); + size_t pushAssistNowData(size_t offset, bool skipTime, const uint8_t *dataBytes, size_t numDataBytes, sfe_ublox_mga_assist_ack_e mgaAck = SFE_UBLOX_MGA_ASSIST_ACK_NO, uint16_t maxWait = defaultMGAdelay); + + // Provide initial time assistance + #define defaultMGAINITIMEtAccS 2 // Default to setting the seconds time accuracy to 2 seconds + #define defaultMGAINITIMEtAccNs 0 // Default to setting the nanoseconds time accuracy to zero + #define defaultMGAINITIMEsource 0 // Set default source to none, i.e. on receipt of message (will be inaccurate!) + bool setUTCTimeAssistance(uint16_t year, uint8_t month, uint8_t day, uint8_t hour, uint8_t minute, uint8_t second, uint32_t nanos = 0, uint16_t tAccS = defaultMGAINITIMEtAccS, uint32_t tAccNs = defaultMGAINITIMEtAccNs, uint8_t source = defaultMGAINITIMEsource, sfe_ublox_mga_assist_ack_e mgaAck = SFE_UBLOX_MGA_ASSIST_ACK_NO, uint16_t maxWait = defaultMGAdelay); + + // Provide initial position assistance + // The units for ecefX/Y/Z and posAcc (stddev) are cm. + bool setPositionAssistanceXYZ(int32_t ecefX, int32_t ecefY, int32_t ecefZ, uint32_t posAcc, sfe_ublox_mga_assist_ack_e mgaAck = SFE_UBLOX_MGA_ASSIST_ACK_NO, uint16_t maxWait = defaultMGAdelay); + // The units for lat and lon are degrees * 1e-7 (WGS84) + // The units for alt (WGS84) and posAcc (stddev) are cm. + bool setPositionAssistanceLLH(int32_t lat, int32_t lon, int32_t alt, uint32_t posAcc, sfe_ublox_mga_assist_ack_e mgaAck = SFE_UBLOX_MGA_ASSIST_ACK_NO, uint16_t maxWait = defaultMGAdelay); + + // Find the start of the AssistNow Offline (UBX_MGA_ANO) data for the chosen day + // The daysIntoFture parameter makes it easy to get the data for (e.g.) tomorrow based on today's date + // Returns numDataBytes if unsuccessful + // TO DO: enhance this so it will find the nearest data for the chosen day - instead of an exact match + size_t findMGAANOForDate(const String &dataBytes, size_t numDataBytes, uint16_t year, uint8_t month, uint8_t day, uint8_t daysIntoFuture = 0); + size_t findMGAANOForDate(const uint8_t *dataBytes, size_t numDataBytes, uint16_t year, uint8_t month, uint8_t day, uint8_t daysIntoFuture = 0); + + // Read the whole navigation data base. The receiver will send all available data from its internal database. + // Data is written to dataBytes. Set maxNumDataBytes to the (maximum) size of dataBytes. + // If the database exceeds maxNumDataBytes, the excess bytes will be lost. + // The function returns the number of database bytes written to dataBytes. + // The return value will be equal to maxNumDataBytes if excess data was received. + // The function will timeout after maxWait milliseconds - in case the final UBX-MGA-ACK was missed. + #define defaultNavDBDMaxWait 3100 + size_t readNavigationDatabase(uint8_t *dataBytes, size_t maxNumDataBytes, uint16_t maxWait = defaultNavDBDMaxWait); + // Support for data logging void setFileBufferSize(uint16_t bufferSize); // Set the size of the file buffer. This must be called _before_ .begin. uint16_t getFileBufferSize(void); // Return the size of the file buffer @@ -785,6 +852,15 @@ class SFE_UBLOX_GNSS bool getTimePulseParameters(UBX_CFG_TP5_data_t *data = NULL, uint16_t maxWait = defaultMaxWait); // Get the time pulse parameters using UBX_CFG_TP5 bool setTimePulseParameters(UBX_CFG_TP5_data_t *data = NULL, uint16_t maxWait = defaultMaxWait); // Set the time pulse parameters using UBX_CFG_TP5 + //UBX-CFG-NAVX5 - get/set the ackAiding byte. If ackAiding is 1, UBX-MGA-ACK messages will be sent by the module to acknowledge the MGA data + uint8_t getAckAiding(uint16_t maxWait = defaultMaxWait); // Get the ackAiding byte - returns 255 if the sendCommand fails + bool setAckAiding(uint8_t ackAiding, uint16_t maxWait = defaultMaxWait); // Set the ackAiding byte + + //AssistNow Autonomous support + //UBX-CFG-NAVX5 - get/set the aopCfg byte and set the aopOrdMaxErr word. If aopOrbMaxErr is 0 (default), the max orbit error is reset to the firmware default. + uint8_t getAopCfg(uint16_t maxWait = defaultMaxWait); // Get the AssistNow Autonomous configuration (aopCfg) - returns 255 if the sendCommand fails + bool setAopCfg(uint8_t aopCfg, uint16_t aopOrbMaxErr = 0, uint16_t maxWait = defaultMaxWait); // Set the aopCfg byte and the aopOrdMaxErr word + //General configuration (used only on protocol v27 and higher - ie, ZED-F9P) //It is probably safe to assume that users of the ZED-F9P will be using I2C / Qwiic. @@ -811,15 +887,15 @@ class SFE_UBLOX_GNSS uint8_t sendCfgValset16(uint32_t keyID, uint16_t value, uint16_t maxWait = 250); //Add the final KeyID and 16-bit value to an existing UBX-CFG-VALSET ubxPacket and send it uint8_t sendCfgValset32(uint32_t keyID, uint32_t value, uint16_t maxWait = 250); //Add the final KeyID and 32-bit value to an existing UBX-CFG-VALSET ubxPacket and send it -// getPVT will only return data once in each navigation cycle. By default, that is once per second. -// Therefore we should set defaultMaxWait to slightly longer than that. -// If you change the navigation frequency to (e.g.) 4Hz using setNavigationFrequency(4) -// then you should use a shorter maxWait. 300msec would be about right: getPVT(300) - // get and set functions for all of the "automatic" message processing // Navigation (NAV) + // getPVT will only return data once in each navigation cycle. By default, that is once per second. + // Therefore we should set defaultMaxWait to slightly longer than that. + // If you change the navigation frequency to (e.g.) 4Hz using setNavigationFrequency(4) + // then you should use a shorter maxWait. 300msec would be about right: getPVT(300) + bool getNAVPOSECEF(uint16_t maxWait = defaultMaxWait); // NAV POSECEF bool setAutoNAVPOSECEF(bool enabled, uint16_t maxWait = defaultMaxWait); //Enable/disable automatic POSECEF reports at the navigation frequency bool setAutoNAVPOSECEF(bool enabled, bool implicitUpdate, uint16_t maxWait = defaultMaxWait); //Enable/disable automatic POSECEF reports at the navigation frequency, with implicitUpdate == false accessing stale data will not issue parsing of data in the rxbuffer of your interface, instead you have to call checkUblox when you want to perform an update @@ -926,6 +1002,15 @@ class SFE_UBLOX_GNSS // Add "auto" support for NAV TIMELS - to avoid needing 'global' storage bool getLeapSecondEvent(uint16_t maxWait); //Reads leap second event info + bool getNAVSAT(uint16_t maxWait = defaultMaxWait); //Query module for latest AssistNow Autonomous status and load global vars:. If autoNAVSAT is disabled, performs an explicit poll and waits, if enabled does not block. Returns true if new NAVSAT is available. + bool setAutoNAVSAT(bool enabled, uint16_t maxWait = defaultMaxWait); //Enable/disable automatic NAVSAT reports at the navigation frequency + bool setAutoNAVSAT(bool enabled, bool implicitUpdate, uint16_t maxWait = defaultMaxWait); //Enable/disable automatic NAVSAT reports at the navigation frequency, with implicitUpdate == false accessing stale data will not issue parsing of data in the rxbuffer of your interface, instead you have to call checkUblox when you want to perform an update + bool setAutoNAVSATrate(uint8_t rate, bool implicitUpdate = true, uint16_t maxWait = defaultMaxWait); //Set the rate for automatic NAVSAT reports + bool setAutoNAVSATcallback(void (*callbackPointer)(UBX_NAV_SAT_data_t), uint16_t maxWait = defaultMaxWait); //Enable automatic NAVSAT reports at the navigation frequency. Data is accessed from the callback. + bool assumeAutoNAVSAT(bool enabled, bool implicitUpdate = true); //In case no config access to the GPS is possible and NAVSAT is send cyclically already + void flushNAVSAT(); //Mark all the NAVSAT data as read/stale + void logNAVSAT(bool enabled = true); // Log data to file buffer + bool getRELPOSNED(uint16_t maxWait = defaultMaxWait); //Get Relative Positioning Information of the NED frame bool setAutoRELPOSNED(bool enabled, uint16_t maxWait = defaultMaxWait); //Enable/disable automatic RELPOSNED reports bool setAutoRELPOSNED(bool enabled, bool implicitUpdate, uint16_t maxWait = defaultMaxWait); //Enable/disable automatic RELPOSNED, with implicitUpdate == false accessing stale data will not issue parsing of data in the rxbuffer of your interface, instead you have to call checkUblox when you want to perform an update @@ -935,6 +1020,15 @@ class SFE_UBLOX_GNSS void flushNAVRELPOSNED(); //Mark all the data as read/stale void logNAVRELPOSNED(bool enabled = true); // Log data to file buffer + bool getAOPSTATUS(uint16_t maxWait = defaultMaxWait); //Query module for latest AssistNow Autonomous status and load global vars:. If autoAOPSTATUS is disabled, performs an explicit poll and waits, if enabled does not block. Returns true if new AOPSTATUS is available. + bool setAutoAOPSTATUS(bool enabled, uint16_t maxWait = defaultMaxWait); //Enable/disable automatic AOPSTATUS reports at the navigation frequency + bool setAutoAOPSTATUS(bool enabled, bool implicitUpdate, uint16_t maxWait = defaultMaxWait); //Enable/disable automatic AOPSTATUS reports at the navigation frequency, with implicitUpdate == false accessing stale data will not issue parsing of data in the rxbuffer of your interface, instead you have to call checkUblox when you want to perform an update + bool setAutoAOPSTATUSrate(uint8_t rate, bool implicitUpdate = true, uint16_t maxWait = defaultMaxWait); //Set the rate for automatic AOPSTATUS reports + bool setAutoAOPSTATUScallback(void (*callbackPointer)(UBX_NAV_AOPSTATUS_data_t), uint16_t maxWait = defaultMaxWait); //Enable automatic AOPSTATUS reports at the navigation frequency. Data is accessed from the callback. + bool assumeAutoAOPSTATUS(bool enabled, bool implicitUpdate = true); //In case no config access to the GPS is possible and AOPSTATUS is send cyclically already + void flushAOPSTATUS(); //Mark all the AOPSTATUS data as read/stale + void logAOPSTATUS(bool enabled = true); // Log data to file buffer + // Receiver Manager Messages (RXM) bool getRXMSFRBX(uint16_t maxWait = defaultMaxWait); // RXM SFRBX @@ -1178,6 +1272,11 @@ class SFE_UBLOX_GNSS float getRelPosAccE(uint16_t maxWait = defaultMaxWait); // Returned as m float getRelPosAccD(uint16_t maxWait = defaultMaxWait); // Returned as m + // Helper functions for AOPSTATUS + + uint8_t getAOPSTATUSuseAOP(uint16_t maxWait = defaultMaxWait); // Returns the UBX-NAV-AOPSTATUS useAOP flag. Don't confuse this with getAopCfg - which returns the aopCfg byte from UBX-CFG-NAVX5 + uint8_t getAOPSTATUSstatus(uint16_t maxWait = defaultMaxWait); // Returns the UBX-NAV-AOPSTATUS status field. A host application can determine the optimal time to shut down the receiver by monitoring the status field for a steady 0. + // Helper functions for ESF float getESFroll(uint16_t maxWait = defaultMaxWait); // Returned as degrees @@ -1223,7 +1322,9 @@ class SFE_UBLOX_GNSS UBX_NAV_CLOCK_t *packetUBXNAVCLOCK = NULL; // Pointer to struct. RAM will be allocated for this if/when necessary UBX_NAV_TIMELS_t *packetUBXNAVTIMELS = NULL; // Pointer to struct. RAM will be allocated for this if/when necessary UBX_NAV_SVIN_t *packetUBXNAVSVIN = NULL; // Pointer to struct. RAM will be allocated for this if/when necessary + UBX_NAV_SAT_t *packetUBXNAVSAT = NULL; // Pointer to struct. RAM will be allocated for this if/when necessary UBX_NAV_RELPOSNED_t *packetUBXNAVRELPOSNED = NULL; // Pointer to struct. RAM will be allocated for this if/when necessary + UBX_NAV_AOPSTATUS_t *packetUBXNAVAOPSTATUS = NULL; // Pointer to struct. RAM will be allocated for this if/when necessary UBX_RXM_SFRBX_t *packetUBXRXMSFRBX = NULL; // Pointer to struct. RAM will be allocated for this if/when necessary UBX_RXM_RAWX_t *packetUBXRXMRAWX = NULL; // Pointer to struct. RAM will be allocated for this if/when necessary @@ -1242,6 +1343,9 @@ class SFE_UBLOX_GNSS UBX_HNR_ATT_t *packetUBXHNRATT = NULL; // Pointer to struct. RAM will be allocated for this if/when necessary UBX_HNR_INS_t *packetUBXHNRINS = NULL; // Pointer to struct. RAM will be allocated for this if/when necessary + UBX_MGA_ACK_DATA0_t *packetUBXMGAACK = NULL; // Pointer to struct. RAM will be allocated for this if/when necessary + UBX_MGA_DBD_t *packetUBXMGADBD = NULL; // Pointer to struct. RAM will be allocated for this if/when necessary + uint16_t rtcmFrameCounter = 0; //Tracks the type of incoming byte inside RTCM frame private: @@ -1271,7 +1375,9 @@ class SFE_UBLOX_GNSS //Functions bool checkUbloxInternal(ubxPacket *incomingUBX, uint8_t requestedClass = 255, uint8_t requestedID = 255); //Checks module with user selected commType - void addToChecksum(uint8_t incoming); //Given an incoming byte, adjust rollingChecksumA/B + void addToChecksum(uint8_t incoming); //Given an incoming byte, adjust rollingChecksumA/B + size_t pushAssistNowDataInternal(size_t offset, bool skipTime, const uint8_t *dataBytes, size_t numDataBytes, sfe_ublox_mga_assist_ack_e mgaAck, uint16_t maxWait); + size_t findMGAANOForDateInternal(const uint8_t *dataBytes, size_t numDataBytes, uint16_t year, uint8_t month, uint8_t day, uint8_t daysIntoFuture); //Return true if this "automatic" message has storage allocated for it bool checkAutomatic(uint8_t Class, uint8_t ID); @@ -1300,7 +1406,9 @@ class SFE_UBLOX_GNSS bool initPacketUBXNAVCLOCK(); // Allocate RAM for packetUBXNAVCLOCK and initialize it bool initPacketUBXNAVTIMELS(); // Allocate RAM for packetUBXNAVTIMELS and initialize it bool initPacketUBXNAVSVIN(); // Allocate RAM for packetUBXNAVSVIN and initialize it + bool initPacketUBXNAVSAT(); // Allocate RAM for packetUBXNAVSAT and initialize it bool initPacketUBXNAVRELPOSNED(); // Allocate RAM for packetUBXNAVRELPOSNED and initialize it + bool initPacketUBXNAVAOPSTATUS(); // Allocate RAM for packetUBXNAVAOPSTATUS and initialize it bool initPacketUBXRXMSFRBX(); // Allocate RAM for packetUBXRXMSFRBX and initialize it bool initPacketUBXRXMRAWX(); // Allocate RAM for packetUBXRXMRAWX and initialize it bool initPacketUBXCFGRATE(); // Allocate RAM for packetUBXCFGRATE and initialize it @@ -1313,6 +1421,8 @@ class SFE_UBLOX_GNSS bool initPacketUBXHNRATT(); // Allocate RAM for packetUBXHNRATT and initialize it bool initPacketUBXHNRINS(); // Allocate RAM for packetUBXHNRINS and initialize it bool initPacketUBXHNRPVT(); // Allocate RAM for packetUBXHNRPVT and initialize it + bool initPacketUBXMGAACK(); // Allocate RAM for packetUBXMGAACK and initialize it + bool initPacketUBXMGADBD(); // Allocate RAM for packetUBXMGADBD and initialize it //Variables TwoWire *_i2cPort; //The generic connection to user's chosen I2C hardware diff --git a/src/u-blox_structs.h b/src/u-blox_structs.h index 763bdb6..be78cfe 100644 --- a/src/u-blox_structs.h +++ b/src/u-blox_structs.h @@ -65,6 +65,8 @@ struct ubxAutomaticFlags } flags; }; +// NAV-specific structs + // UBX-NAV-POSECEF (0x01 0x01): Position solution in ECEF const uint16_t UBX_NAV_POSECEF_LEN = 20; @@ -914,6 +916,79 @@ typedef struct UBX_NAV_TIMELS_data_t *callbackData; } UBX_NAV_TIMELS_t; +// UBX-NAV-SAT (0x01 0x35): Satellite Information +const uint16_t UBX_NAV_SAT_MAX_BLOCKS = 256; // TO DO: confirm if this is large enough for all modules +const uint16_t UBX_NAV_SAT_MAX_LEN = 8 + (12 * UBX_NAV_SAT_MAX_BLOCKS); + +typedef struct +{ + uint32_t iTOW; // GPS time of week + uint8_t version; // Message version (0x01 for this version) + uint8_t numSvs; // Number of satellites + uint8_t reserved1[2]; +} UBX_NAV_SAT_header_t; + +typedef struct +{ + uint8_t gnssId; // GNSS identifier + uint8_t svId; // Satellite identifier + uint8_t cno; // Carrier-to-noise density ratio: dB-Hz + int8_t elev; // Elevation (range: +/-90): deg + int16_t azim; // Azimuth (range 0-360): deg + int16_t prRes; // Pseudorange residual: m * 0.1 + union + { + uint32_t all; + struct + { + uint32_t qualityInd : 3; // Signal quality indicator: 0: no signal + // 1: searching signal + // 2: signal acquired + // 3: signal detected but unusable + // 4: code locked and time synchronized + // 5, 6, 7: code and carrier locked and time synchronized + uint32_t svUsed : 1; // 1 = Signal in the subset specified in Signal Identifiers is currently being used for navigation + uint32_t health : 2; // Signal health flag: 0: unknown 1: healthy 2: unhealthy + uint32_t diffCorr : 1; // 1 = differential correction data is available for this SV + uint32_t smoothed : 1; // 1 = carrier smoothed pseudorange used + uint32_t orbitSource : 3; // Orbit source: 0: no orbit information is available for this SV + // 1: ephemeris is used + // 2: almanac is used + // 3: AssistNow Offline orbit is used + // 4: AssistNow Autonomous orbit is used + // 5, 6, 7: other orbit information is used + uint32_t ephAvail : 1; // 1 = ephemeris is available for this SV + uint32_t almAvail : 1; // 1 = almanac is available for this SV + uint32_t anoAvail : 1; // 1 = AssistNow Offline data is available for this SV + uint32_t aopAvail : 1; // 1 = AssistNow Autonomous data is available for this SV + uint32_t reserved1 : 1; + uint32_t sbasCorrUsed : 1; // 1 = SBAS corrections have been used for a signal in the subset specified in Signal Identifiers + uint32_t rtcmCorrUsed : 1; // 1 = RTCM corrections have been used for a signal in the subset specified in Signal Identifiers + uint32_t slasCorrUsed : 1; // 1 = QZSS SLAS corrections have been used for a signal in the subset specified in Signal Identifiers + uint32_t spartnCorrUsed : 1; // 1 = SPARTN corrections have been used for a signal in the subset specified in Signal Identifiers + uint32_t prCorrUsed : 1; // 1 = Pseudorange corrections have been used for a signal in the subset specified in Signal Identifiers + uint32_t crCorrUsed : 1; // 1 = Carrier range corrections have been used for a signal in the subset specified in Signal Identifiers + uint32_t doCorrUsed : 1; // 1 = Range rate (Doppler) corrections have been used for a signal in the subset specified in Signal Identifiers + uint32_t reserved2 : 9; + } bits; + } flags; +} UBX_NAV_SAT_block_t; + +typedef struct +{ + UBX_NAV_SAT_header_t header; + UBX_NAV_SAT_block_t blocks[UBX_NAV_SAT_MAX_BLOCKS]; +} UBX_NAV_SAT_data_t; + +typedef struct +{ + ubxAutomaticFlags automaticFlags; + UBX_NAV_SAT_data_t data; + bool moduleQueried; + void (*callbackPointer)(UBX_NAV_SAT_data_t); + UBX_NAV_SAT_data_t *callbackData; +} UBX_NAV_SAT_t; + // UBX-NAV-SVIN (0x01 0x3B): Survey-in data const uint16_t UBX_NAV_SVIN_LEN = 40; @@ -1071,6 +1146,51 @@ typedef struct UBX_NAV_RELPOSNED_data_t *callbackData; } UBX_NAV_RELPOSNED_t; +// UBX-NAV-AOPSTATUS (0x01 0x60): AssistNow Autonomous status +const uint16_t UBX_NAV_AOPSTATUS_LEN = 16; + +typedef struct +{ + uint32_t iTOW; // GPS time of week of the navigation epoch: ms + union + { + uint8_t all; + struct + { + uint8_t useAOP : 1; // AOP enabled flag + } bits; + } aopCfg; // AssistNow Autonomous configuration + uint8_t status; // AssistNow Autonomous subsystem is idle (0) or running (not 0) + uint8_t reserved1[10]; +} UBX_NAV_AOPSTATUS_data_t; + +typedef struct +{ + union + { + uint32_t all; + struct + { + uint32_t all : 1; + + uint32_t iTOW : 1; + + uint32_t useAOP : 1; + + uint32_t status : 1; + } bits; + } moduleQueried; +} UBX_NAV_AOPSTATUS_moduleQueried_t; + +typedef struct +{ + ubxAutomaticFlags automaticFlags; + UBX_NAV_AOPSTATUS_data_t data; + UBX_NAV_AOPSTATUS_moduleQueried_t moduleQueried; + void (*callbackPointer)(UBX_NAV_AOPSTATUS_data_t); + UBX_NAV_AOPSTATUS_data_t *callbackData; +} UBX_NAV_AOPSTATUS_t; + // RXM-specific structs // UBX-RXM-SFRBX (0x02 0x13): Broadcast navigation data subframe @@ -1656,6 +1776,55 @@ typedef struct UBX_ESF_STATUS_data_t *callbackData; } UBX_ESF_STATUS_t; +// MGA-specific structs + +// UBX-MGA-ACK-DATA0 (0x13 0x60): Multiple GNSS acknowledge message +const uint16_t UBX_MGA_ACK_DATA0_LEN = 8; + +typedef struct +{ + uint8_t type; // Type of acknowledgment: + // 0: The message was not used by the receiver (see infoCode field for an indication of why) + // 1: The message was accepted for use by the receiver (the infoCode field will be 0) + uint8_t version; // Message version + uint8_t infoCode; // Provides greater information on what the receiver chose to do with the message contents + // See sfe_ublox_mga_ack_infocode_e + uint8_t msgId; // UBX message ID of the acknowledged message + uint8_t msgPayloadStart[4]; // The first 4 bytes of the acknowledged message's payload +} UBX_MGA_ACK_DATA0_data_t; + +#define UBX_MGA_ACK_DATA0_RINGBUFFER_LEN 16 // Provide storage for 16 MGA ACK packets +typedef struct +{ + uint8_t head; + uint8_t tail; + UBX_MGA_ACK_DATA0_data_t data[UBX_MGA_ACK_DATA0_RINGBUFFER_LEN]; // Create a storage array for the MGA ACK packets +} UBX_MGA_ACK_DATA0_t; + +// UBX-MGA-DBD (0x13 0x80): Navigation database dump entry +const uint16_t UBX_MGA_DBD_LEN = 164; // "The maximum payload size for firmware 2.01 onwards is 164 bytes" + +typedef struct +{ + uint8_t dbdEntryHeader1; // We need to save the entire message - header, payload and checksum + uint8_t dbdEntryHeader2; + uint8_t dbdEntryClass; + uint8_t dbdEntryID; + uint8_t dbdEntryLenLSB; // We need to store the length of the DBD entry. The entry itself does not contain a length... + uint8_t dbdEntryLenMSB; + uint8_t dbdEntry[UBX_MGA_DBD_LEN]; + uint8_t dbdEntryChecksumA; + uint8_t dbdEntryChecksumB; +} UBX_MGA_DBD_data_t; + +#define UBX_MGA_DBD_RINGBUFFER_LEN 256 // Provide storage for MGA DBD packets. TO DO: confirm if 256 is large enough! +typedef struct +{ + uint8_t head; + uint8_t tail; + UBX_MGA_DBD_data_t data[UBX_MGA_DBD_RINGBUFFER_LEN]; // Create a storage array for the MGA DBD packets +} UBX_MGA_DBD_t; + // HNR-specific structs // UBX-HNR-PVT (0x28 0x00): High rate output of PVT solution