diff --git a/examples/AssistNow/AssistNow_Online/Example5_AssistNowOnline_MQTT/Example5_AssistNowOnline_MQTT.ino b/examples/AssistNow/AssistNow_Online/Example5_AssistNowOnline_MQTT/Example5_AssistNowOnline_MQTT.ino index 99d9b78..4401f44 100644 --- a/examples/AssistNow/AssistNow_Online/Example5_AssistNowOnline_MQTT/Example5_AssistNowOnline_MQTT.ino +++ b/examples/AssistNow/AssistNow_Online/Example5_AssistNowOnline_MQTT/Example5_AssistNowOnline_MQTT.ino @@ -93,27 +93,43 @@ void loop() WiFiClientSecure wifiClient = WiFiClientSecure(); MqttClient mqttClient(wifiClient); -void mqttMessageHandler(int messageSize) { - uint8_t mgaData[512 * 4]; //Most incoming data is around 500 bytes but may be larger - int mgaCount = 0; - Serial.print(F("Pushed data from ")); +void mqttMessageHandler(int messageSize) +{ + const uint16_t mqttLimit = 512; + uint8_t *mqttData = new uint8_t[mqttLimit]; // Allocate memory to hold the MQTT data + if (mqttData == NULL) + { + Serial.println(F("Memory allocation for mqttData failed!")); + return; + } + + Serial.print(F("Pushing data from ")); Serial.print(mqttClient.messageTopic()); Serial.println(F(" topic to ZED")); + while (mqttClient.available()) { - char ch = mqttClient.read(); - //Serial.write(ch); //Pipe to serial port is fine but beware, it's a lot of binary data - mgaData[mgaCount++] = ch; - if (mgaCount == sizeof(mgaData)) - break; - } + uint16_t mqttCount = 0; - if (mgaCount > 0) - { - //Push MGA data to GNSS module over I2C - myGNSS.pushRawData(mgaData, mgaCount, false); - lastReceived_ms = millis(); + while (mqttClient.available()) + { + char ch = mqttClient.read(); + //Serial.write(ch); //Pipe to serial port is fine but beware, it's a lot of binary data + mqttData[mqttCount++] = ch; + + if (mqttCount == mqttLimit) + break; + } + + if (mqttCount > 0) + { + //Push KEYS or SPARTN data to GNSS module over I2C + myGNSS.pushRawData(mqttData, mqttCount, false); + lastReceived_ms = millis(); + } } + + delete[] mqttData; } //Connect to MQTT broker, receive MGA, and push to ZED module over I2C diff --git a/examples/Example30_NEO-D9S/Example30_NEO-D9S.ino b/examples/Example30_NEO-D9S/Example30_NEO-D9S.ino index fa971d6..8a6fe89 100644 --- a/examples/Example30_NEO-D9S/Example30_NEO-D9S.ino +++ b/examples/Example30_NEO-D9S/Example30_NEO-D9S.ino @@ -10,7 +10,7 @@ Feel like supporting open source hardware? Buy a board from SparkFun! ZED-F9P RTK2: https://www.sparkfun.com/products/16481 - NEO-D9S: Coming soon! + NEO-D9S Correction Data Receiver: https://www.sparkfun.com/products/19390 Hardware Connections: Use a Qwiic cable to connect the NEO-D9S L-Band corection data receiver to your board diff --git a/examples/ZED-F9P/Example18_PointPerfectClient/Example18_PointPerfectClient.ino b/examples/ZED-F9P/Example18_PointPerfectClient/Example18_PointPerfectClient.ino index 2edc5bb..9188f72 100644 --- a/examples/ZED-F9P/Example18_PointPerfectClient/Example18_PointPerfectClient.ino +++ b/examples/ZED-F9P/Example18_PointPerfectClient/Example18_PointPerfectClient.ino @@ -36,6 +36,7 @@ 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 // Click here to get the library: http://librarymanager/All#ArduinoMqttClient @@ -44,10 +45,137 @@ #include // Click here to get the library: http://librarymanager/All#SparkFun_u-blox_GNSS SFE_UBLOX_GNSS myGNSS; -//Global variables +#define OK(ok) (ok ? F(" -> OK") : F(" -> ERROR!")) // Convert uint8_t into OK/ERROR + //=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + +//Global variables + long lastReceived_ms = 0; //5 RTCM messages take approximately ~300ms to arrive at 115200bps int maxTimeBeforeHangup_ms = 10000; //If we fail to get a complete RTCM frame after 10s, then disconnect from caster + +//=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + +// 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 setAutoPVTcallbackPtr +// / _____ This _must_ be UBX_NAV_PVT_data_t +// | / _____ You can use any name you like for the struct +// | | / +// | | | +void printPVTdata(UBX_NAV_PVT_data_t *ubxDataStruct) +{ + double latitude = ubxDataStruct->lat; // Print the latitude + Serial.print(F("Lat: ")); + Serial.print(latitude / 10000000.0, 7); + + double longitude = ubxDataStruct->lon; // Print the longitude + Serial.print(F(" Long: ")); + Serial.print(longitude / 10000000.0, 7); + + double altitude = ubxDataStruct->hMSL; // Print the height above mean sea level + Serial.print(F(" Height: ")); + Serial.print(altitude / 1000.0, 3); + + uint8_t fixType = ubxDataStruct->fixType; // Print the fix type + Serial.print(F(" Fix: ")); + Serial.print(fixType); + if (fixType == 0) + Serial.print(F(" (None)")); + else if (fixType == 1) + Serial.print(F(" (Dead Reckoning)")); + else if (fixType == 2) + Serial.print(F(" (2D)")); + else if (fixType == 3) + Serial.print(F(" (3D)")); + else if (fixType == 3) + Serial.print(F(" (GNSS + Dead Reckoning)")); + else if (fixType == 5) + Serial.print(F(" (Time Only)")); + else + Serial.print(F(" (UNKNOWN)")); + + uint8_t carrSoln = ubxDataStruct->flags.bits.carrSoln; // Print the carrier solution + Serial.print(F(" Carrier Solution: ")); + Serial.print(carrSoln); + if (carrSoln == 0) + Serial.print(F(" (None)")); + else if (carrSoln == 1) + Serial.print(F(" (Floating)")); + else if (carrSoln == 2) + Serial.print(F(" (Fixed)")); + else + Serial.print(F(" (UNKNOWN)")); + + uint32_t hAcc = ubxDataStruct->hAcc; // Print the horizontal accuracy estimate + Serial.print(F(" Horizontal Accuracy Estimate: ")); + Serial.print(hAcc); + Serial.print(F(" (mm)")); + + Serial.println(); +} + +//=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + +// Callback: printRXMCOR will be called when new RXM COR data arrives +// See u-blox_structs.h for the full definition of UBX_RXM_COR_data_t +// _____ You can use any name you like for the callback. Use the same name when you call setRXMCORcallbackPtr +// / _____ This _must_ be UBX_RXM_COR_data_t +// | / _____ You can use any name you like for the struct +// | | / +// | | | +void printRXMCOR(UBX_RXM_COR_data_t *ubxDataStruct) +{ + Serial.print(F("UBX-RXM-COR: ebno: ")); + Serial.print(ubxDataStruct->ebno); + + Serial.print(F(" protocol: ")); + if (ubxDataStruct->statusInfo.bits.protocol == 1) + Serial.print(F("RTCM3")); + else if (ubxDataStruct->statusInfo.bits.protocol == 2) + Serial.print(F("SPARTN")); + else if (ubxDataStruct->statusInfo.bits.protocol == 29) + Serial.print(F("PMP (SPARTN)")); + else if (ubxDataStruct->statusInfo.bits.protocol == 30) + Serial.print(F("QZSSL6")); + else + Serial.print(F("Unknown")); + + Serial.print(F(" errStatus: ")); + if (ubxDataStruct->statusInfo.bits.errStatus == 1) + Serial.print(F("Error-free")); + else if (ubxDataStruct->statusInfo.bits.errStatus == 2) + Serial.print(F("Erroneous")); + else + Serial.print(F("Unknown")); + + Serial.print(F(" msgUsed: ")); + if (ubxDataStruct->statusInfo.bits.msgUsed == 1) + Serial.print(F("Not used")); + else if (ubxDataStruct->statusInfo.bits.msgUsed == 2) + Serial.print(F("Used")); + else + Serial.print(F("Unknown")); + + Serial.print(F(" msgEncrypted: ")); + if (ubxDataStruct->statusInfo.bits.msgEncrypted == 1) + Serial.print(F("Not encrypted")); + else if (ubxDataStruct->statusInfo.bits.msgEncrypted == 2) + Serial.print(F("Encrypted")); + else + Serial.print(F("Unknown")); + + Serial.print(F(" msgDecrypted: ")); + if (ubxDataStruct->statusInfo.bits.msgDecrypted == 1) + Serial.print(F("Not decrypted")); + else if (ubxDataStruct->statusInfo.bits.msgDecrypted == 2) + Serial.print(F("Successfully decrypted")); + else + Serial.print(F("Unknown")); + + Serial.println(); +} + //=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= void setup() @@ -58,17 +186,32 @@ void setup() Wire.begin(); //Start I2C - if (myGNSS.begin() == false) //Connect to the Ublox module using Wire port + //myGNSS.enableDebugging(); // Uncomment this line to enable debug messages on Serial + + while (myGNSS.begin() == false) //Connect to the u-blox 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 GNSS module not detected at default I2C address. Please check wiring.")); + delay(2000); } - - Serial.println(F("u-blox module connected")); - myGNSS.setI2COutput(COM_TYPE_UBX); //Turn off NMEA noise - myGNSS.setPortInput(COM_PORT_I2C, COM_TYPE_UBX | COM_TYPE_NMEA | COM_TYPE_SPARTN); // Be sure SPARTN input is enabled. - - myGNSS.setNavigationFrequency(1); //Set output in Hz. + Serial.println(F("u-blox GNSS module connected")); + + uint8_t ok = myGNSS.setI2COutput(COM_TYPE_UBX); //Turn off NMEA noise + if (ok) ok = myGNSS.setPortInput(COM_PORT_I2C, COM_TYPE_UBX | COM_TYPE_NMEA | COM_TYPE_SPARTN); // Be sure SPARTN input is enabled. + + if (ok) ok = myGNSS.setDGNSSConfiguration(SFE_UBLOX_DGNSS_MODE_FIXED); // Set the differential mode - ambiguities are fixed whenever possible + if (ok) ok = myGNSS.setNavigationFrequency(1); //Set output in Hz. + if (ok) ok = myGNSS.setVal8(UBLOX_CFG_SPARTN_USE_SOURCE, 0); // Use IP source (default). Change this to 1 for L-Band (PMP) + + if (ok) ok = myGNSS.setAutoPVTcallbackPtr(&printPVTdata); // Enable automatic NAV PVT messages with callback to printPVTdata so we can watch the carrier solution go to fixed + + if (ok) ok = myGNSS.setVal8(UBLOX_CFG_MSGOUT_UBX_RXM_COR_I2C, 1); // Enable UBX-RXM-COR messages on I2C + if (ok) ok = myGNSS.setRXMCORcallbackPtr(&printRXMCOR); // Print the contents of UBX-RXM-COR messages so we can check if the SPARTN data is being decrypted successfully + + //if (ok) ok = myGNSS.saveConfiguration(VAL_CFG_SUBSEC_IOPORT | VAL_CFG_SUBSEC_MSGCONF); //Optional: Save the ioPort and message settings to NVM + + Serial.print(F("GNSS: configuration ")); + Serial.println(OK(ok)); + Serial.print(F("Connecting to local WiFi")); WiFi.begin(ssid, password); while (WiFi.status() != WL_CONNECTED) { @@ -81,6 +224,9 @@ void setup() Serial.println(WiFi.localIP()); while (Serial.available()) Serial.read(); + + Serial.println(F("Press any key to start MQTT/SPARTN Client.")); + } void loop() @@ -88,38 +234,56 @@ void loop() if (Serial.available()) { beginClient(); + while (Serial.available()) Serial.read(); //Empty buffer of any newline chars + + Serial.println(F("Press any key to start MQTT/SPARTN Client.")); } - Serial.println(F("Press any key to start MQTT/SPARTN Client.")); - - delay(1000); + myGNSS.checkUblox(); // Check for the arrival of new GNSS data and process it. + myGNSS.checkCallbacks(); // Check if any GNSS callbacks are waiting to be processed. } WiFiClientSecure wifiClient = WiFiClientSecure(); MqttClient mqttClient(wifiClient); -void mqttMessageHandler(int messageSize) { - uint8_t spartnData[512 * 4]; //Most incoming data is around 500 bytes but may be larger - int spartnCount = 0; - Serial.print(F("Pushed data from ")); +void mqttMessageHandler(int messageSize) +{ + const uint16_t mqttLimit = 512; + uint8_t *mqttData = new uint8_t[mqttLimit]; // Allocate memory to hold the MQTT data + if (mqttData == NULL) + { + Serial.println(F("Memory allocation for mqttData failed!")); + return; + } + + Serial.print(F("Pushing data from ")); Serial.print(mqttClient.messageTopic()); Serial.println(F(" topic to ZED")); + while (mqttClient.available()) { - char ch = mqttClient.read(); - //Serial.write(ch); //Pipe to serial port is fine but beware, it's a lot of binary data - spartnData[spartnCount++] = ch; - if (spartnCount == sizeof(spartnData)) - break; - } + uint16_t mqttCount = 0; - if (spartnCount > 0) - { - //Push KEYS or SPARTN data to GNSS module over I2C - myGNSS.pushRawData(spartnData, spartnCount, false); - lastReceived_ms = millis(); + while (mqttClient.available()) + { + char ch = mqttClient.read(); + //Serial.write(ch); //Pipe to serial port is fine but beware, it's a lot of binary data + mqttData[mqttCount++] = ch; + + if (mqttCount == mqttLimit) + break; + } + + if (mqttCount > 0) + { + //Push KEYS or SPARTN data to GNSS module over I2C + myGNSS.pushRawData(mqttData, mqttCount, false); + lastReceived_ms = millis(); + } } + + delete[] mqttData; } //Connect to STARTN MQTT broker, receive RTCM, and push to ZED module over I2C @@ -153,12 +317,14 @@ void beginClient() mqttClient.onMessage(mqttMessageHandler); mqttClient.subscribe(MQTT_TOPIC_KEY); mqttClient.subscribe(MQTT_TOPIC_SPARTN); + mqttClient.subscribe(MQTT_TOPIC_ASSISTNOW); lastReceived_ms = millis(); } //End attempt to connect } //End connected == false else { mqttClient.poll(); } + //Close socket if we don't have new data for 10s if (millis() - lastReceived_ms > maxTimeBeforeHangup_ms) { @@ -168,6 +334,9 @@ void beginClient() return; } + myGNSS.checkUblox(); // Check for the arrival of new GNSS data and process it. + myGNSS.checkCallbacks(); // Check if any GNSS callbacks are waiting to be processed. + delay(10); } diff --git a/examples/ZED-F9P/Example18_PointPerfectClient/secrets.h b/examples/ZED-F9P/Example18_PointPerfectClient/secrets.h index d206b1c..d7c27ea 100644 --- a/examples/ZED-F9P/Example18_PointPerfectClient/secrets.h +++ b/examples/ZED-F9P/Example18_PointPerfectClient/secrets.h @@ -5,15 +5,19 @@ const char password[] = ""; // Below infomation you can set after signing up with u-blox Thingstream portal // and after add a new New PointPerfect Thing // https://portal.thingstream.io/app/location-services/things -// in the new PointPerfect Thing you go to the credentials page and copy past the values and certificate into this. +// in the new PointPerfect Thing you go to the credentials page and copy paste the values and certificate into this. // -> Credentials -> Hostname const char AWS_IOT_ENDPOINT[] = "pp.services.u-blox.com"; const unsigned short AWS_IOT_PORT = 8883; // -> Credentials -> IP key distribution topic -const char MQTT_TOPIC_KEY[] = "/pp/key/ip"; +const char MQTT_TOPIC_KEY[] = "/pp/ubx/0236/ip"; // This topic provides the IP only dynamic keys in UBX format +//const char MQTT_TOPIC_KEY[] = "/pp/ubx/0236/Lb"; // This topic provides the L-Band + IP dynamic keys in UBX format // -> Credentials -> IP correction topic for EU/US region -const char MQTT_TOPIC_SPARTN[] = "/pp/ip/us"; // choice of {eu, us} +const char MQTT_TOPIC_SPARTN[] = "/pp/ip/us"; // This topic provides the SPARTN corrections for IP only: choice of {eu, us} +//const char MQTT_TOPIC_SPARTN[] = "/pp/Lb/us"; // This topic provides the SPARTN corrections for L-Band and L-Band + IP: choice of {eu, us} +// -> Credentials -> AssistNow (MGA) topic +const char MQTT_TOPIC_ASSISTNOW[] = "/pp/ubx/mga"; // -> Credentials -> Client Id static const char MQTT_CLIENT_ID[] = ""; diff --git a/examples/ZED-F9P/Example19_LBand_Corrections_with_NEO-D9S/Example19_LBand_Corrections_with_NEO-D9S.ino b/examples/ZED-F9P/Example19_LBand_Corrections_with_NEO-D9S/Example19_LBand_Corrections_with_NEO-D9S.ino index a642ed7..e927177 100644 --- a/examples/ZED-F9P/Example19_LBand_Corrections_with_NEO-D9S/Example19_LBand_Corrections_with_NEO-D9S.ino +++ b/examples/ZED-F9P/Example19_LBand_Corrections_with_NEO-D9S/Example19_LBand_Corrections_with_NEO-D9S.ino @@ -10,7 +10,7 @@ This is a proof of concept to show how the UBX-RXM-PMP corrections control the accuracy. - You will need a Thingstream PointPerfect account to be able to access the SPARTN Credentials (IP Dynamic Keys). + You will need a Thingstream PointPerfect account to be able to access the SPARTN Credentials (L-Band or L-Band + IP Dynamic Keys). Copy and paste the Current Key and Next Key into secrets.h. Feel like supporting open source hardware? @@ -121,6 +121,67 @@ void printPVTdata(UBX_NAV_PVT_data_t *ubxDataStruct) //=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= +// Callback: printRXMCOR will be called when new RXM COR data arrives +// See u-blox_structs.h for the full definition of UBX_RXM_COR_data_t +// _____ You can use any name you like for the callback. Use the same name when you call setRXMCORcallbackPtr +// / _____ This _must_ be UBX_RXM_COR_data_t +// | / _____ You can use any name you like for the struct +// | | / +// | | | +void printRXMCOR(UBX_RXM_COR_data_t *ubxDataStruct) +{ + Serial.print(F("UBX-RXM-COR: ebno: ")); + Serial.print(ubxDataStruct->ebno); + + Serial.print(F(" protocol: ")); + if (ubxDataStruct->statusInfo.bits.protocol == 1) + Serial.print(F("RTCM3")); + else if (ubxDataStruct->statusInfo.bits.protocol == 2) + Serial.print(F("SPARTN")); + else if (ubxDataStruct->statusInfo.bits.protocol == 29) + Serial.print(F("PMP (SPARTN)")); + else if (ubxDataStruct->statusInfo.bits.protocol == 30) + Serial.print(F("QZSSL6")); + else + Serial.print(F("Unknown")); + + Serial.print(F(" errStatus: ")); + if (ubxDataStruct->statusInfo.bits.errStatus == 1) + Serial.print(F("Error-free")); + else if (ubxDataStruct->statusInfo.bits.errStatus == 2) + Serial.print(F("Erroneous")); + else + Serial.print(F("Unknown")); + + Serial.print(F(" msgUsed: ")); + if (ubxDataStruct->statusInfo.bits.msgUsed == 1) + Serial.print(F("Not used")); + else if (ubxDataStruct->statusInfo.bits.msgUsed == 2) + Serial.print(F("Used")); + else + Serial.print(F("Unknown")); + + Serial.print(F(" msgEncrypted: ")); + if (ubxDataStruct->statusInfo.bits.msgEncrypted == 1) + Serial.print(F("Not encrypted")); + else if (ubxDataStruct->statusInfo.bits.msgEncrypted == 2) + Serial.print(F("Encrypted")); + else + Serial.print(F("Unknown")); + + Serial.print(F(" msgDecrypted: ")); + if (ubxDataStruct->statusInfo.bits.msgDecrypted == 1) + Serial.print(F("Not decrypted")); + else if (ubxDataStruct->statusInfo.bits.msgDecrypted == 2) + Serial.print(F("Successfully decrypted")); + else + Serial.print(F("Unknown")); + + Serial.println(); +} + +//=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + void setup() { Serial.begin(115200); @@ -148,6 +209,8 @@ void setup() if (ok) ok = myGNSS.setNavigationFrequency(1); //Set output in Hz. if (ok) ok = myGNSS.setVal8(UBLOX_CFG_SPARTN_USE_SOURCE, 1); // use LBAND PMP message + + if (ok) ok = myGNSS.setVal8(UBLOX_CFG_MSGOUT_UBX_RXM_COR_I2C, 1); // Enable UBX-RXM-COR messages on I2C //Configure the SPARTN IP Dynamic Keys //"When the receiver boots, the host should send 'current' and 'next' keys in one message." - Use setDynamicSPARTNKeys for this. @@ -164,6 +227,8 @@ void setup() myGNSS.setAutoPVTcallbackPtr(&printPVTdata); // Enable automatic NAV PVT messages with callback to printPVTdata so we can watch the carrier solution go to fixed + myGNSS.setRXMCORcallbackPtr(&printRXMCOR); // Print the contents of UBX-RXM-COR messages so we can check if the PMP data is being decrypted successfully + //=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= // Begin and configure the NEO-D9S L-Band receiver diff --git a/examples/ZED-F9P/Example19_LBand_Corrections_with_NEO-D9S/secrets.h b/examples/ZED-F9P/Example19_LBand_Corrections_with_NEO-D9S/secrets.h index 158e11c..1d118ea 100644 --- a/examples/ZED-F9P/Example19_LBand_Corrections_with_NEO-D9S/secrets.h +++ b/examples/ZED-F9P/Example19_LBand_Corrections_with_NEO-D9S/secrets.h @@ -1,24 +1,27 @@ // You can set the information below after signing up with the u-blox Thingstream portal -// and adding a new New PointPerfect Thing +// and adding a new New PointPerfect Thing (L-Band or L-Band + IP) // https://portal.thingstream.io/app/location-services/things // In the new PointPerfect Thing, you go to the credentials tab and copy and paste the IP Dynamic Keys here. // // The keys are valid from a particular GPS Week Number and Time of Week. // Looking at the credentials tab, the current key expires 23:59 Feb 11th 2022. // This means the next key is valid _from_ Midnight Feb 12th 2022. -// That is GPS Week 2196. The GPS Time of Week in seconds is 518418. +// That is GPS Week 2196. The GPS Time of Week in seconds is 518400. // Working backwards, the current key became valid exactly 4 weeks earlier (Midnight Jan 15th 2022). // // See: https://www.labsat.co.uk/index.php/en/gps-time-calculator // // The keys are given as: 32 hexadecimal digits = 128 bits = 16 Bytes +// +// The next example shows how to retrieve the keys using ESP32 WiFi and MQTT. +// You can cut and paste the keys and GPS week/time-of-week from that example into here. const uint8_t currentKeyLengthBytes = 16; -const char currentDynamicKey[] = "f742bd6b7248043177dd649141d8fb0b"; -const uint16_t currentKeyGPSWeek = 2192; -const uint32_t currentKeyGPSToW = 518418; +const char currentDynamicKey[] = ""; +const uint16_t currentKeyGPSWeek = 2192; // Update this when you add new keys +const uint32_t currentKeyGPSToW = 518400; const uint8_t nextKeyLengthBytes = 16; -const char nextDynamicKey[] = "8206........................29f4"; -const uint16_t nextKeyGPSWeek = 2196; -const uint32_t nextKeyGPSToW = 518418; +const char nextDynamicKey[] = ""; +const uint16_t nextKeyGPSWeek = 2196; // Update this when you add new keys +const uint32_t nextKeyGPSToW = 518400; diff --git a/examples/ZED-F9P/Example20_PMP_with_L-Band_Keys_via_MQTT/Example20_PMP_with_L-Band_Keys_via_MQTT.ino b/examples/ZED-F9P/Example20_PMP_with_L-Band_Keys_via_MQTT/Example20_PMP_with_L-Band_Keys_via_MQTT.ino new file mode 100644 index 0000000..02358ed --- /dev/null +++ b/examples/ZED-F9P/Example20_PMP_with_L-Band_Keys_via_MQTT/Example20_PMP_with_L-Band_Keys_via_MQTT.ino @@ -0,0 +1,459 @@ +/* + Use ESP32 WiFi to get the L-Band dynamic keys from PointPerfect, allowing a ZED-F9x to use + the PMP data from a NEO-D9S correction data receiver. + By: SparkFun / Paul Clark + Based on original code by: u-blox AG / Michael Ammann + Date: March 17th, 2022 + 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 the L-Band dynamic keys from PointPerfect over ESP32 WiFi + and push them over I2C to a ZED-F9x. The ZED will then be able to decrypt the PMP correction data + from a NEO-D9S correction data receiver. + + You can copy the keys directly from the Thingstream portal and paste them into your code - the + previous example shows how to do this - but calculating the "valid from" week and time is a chore. + This example requests the keys for you (using your client key and certificates) via MQTT. + It prints them too, so you can copy and paste them into the previous example if you wish. + + You will need to have a valid u-blox Thingstream account and have a PointPerfect L-Band or L-Band + IP + Location Thing and payed plan. + + Thingstream offers SSR corrections to SPARTN capable RTK receivers such as the u-blox ZED-F9 series + in continental Europe and US. Their Network is planned to be expanded to other regions over the next years. + To sign up, go to: https://portal.thingstream.io/app/location-services/things + + For more information about MQTT, SPARTN and PointPerfect Correction Services + please see: https://www.u-blox.com/en/product/pointperfect + + Feel like supporting open source hardware? + Buy a board from SparkFun! + ZED-F9P RTK2: https://www.sparkfun.com/products/16481 + NEO-D9S Correction Data Receiver: https://www.sparkfun.com/products/19390 + + RTK Surveyor: https://www.sparkfun.com/products/18443 + RTK Express: https://www.sparkfun.com/products/18442 + + Recommended Hardware: + MicroMod GNSS Carrier Board: https://www.sparkfun.com/products/17722 + ESP32 Micromod https://www.sparkfun.com/products/16781 + + 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 // Click here to get the library: http://librarymanager/All#ArduinoMqttClient +#include "secrets.h" + +#include // Click here to get the library: http://librarymanager/All#SparkFun_u-blox_GNSS +SFE_UBLOX_GNSS myGNSS; // ZED-F9x +SFE_UBLOX_GNSS myLBand; // NEO-D9S + +const uint32_t myLBandFreq = 1556290000; // Uncomment this line to use the US SPARTN 1.8 service +//const uint32_t myLBandFreq = 1545260000; // Uncomment this line to use the EU SPARTN 1.8 service + +#define OK(ok) (ok ? F(" -> OK") : F(" -> ERROR!")) // Convert uint8_t into OK/ERROR + +//=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + +//Global variables + +long lastReceived_ms = 0; //5 RTCM messages take approximately ~300ms to arrive at 115200bps +int maxTimeBeforeHangup_ms = 10000; //If we fail to get a complete RTCM frame after 10s, then disconnect from caster + +//=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + +// Callback: pushRXMPMP will be called when new PMP data arrives +// See u-blox_structs.h for the full definition of UBX_RXM_PMP_message_data_t +// _____ You can use any name you like for the callback. Use the same name when you call setRXMPMPmessageCallbackPtr +// / _____ This _must_ be UBX_RXM_PMP_message_data_t +// | / _____ You can use any name you like for the struct +// | | / +// | | | +void pushRXMPMP(UBX_RXM_PMP_message_data_t *pmpData) +{ + //Extract the raw message payload length + uint16_t payloadLen = ((uint16_t)pmpData->lengthMSB << 8) | (uint16_t)pmpData->lengthLSB; + Serial.print(F("New RXM-PMP data received. Message payload length is ")); + Serial.print(payloadLen); + Serial.println(F(" Bytes. Pushing it to the GNSS...")); + + //Push the PMP data to the GNSS + //The payload length could be variable, so we need to push the header and payload, then checksum + myGNSS.pushRawData(&pmpData->sync1, (size_t)payloadLen + 6); // Push the sync chars, class, ID, length and payload + myGNSS.pushRawData(&pmpData->checksumA, (size_t)2); // Push the checksum bytes +} + +//=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + +// 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 setAutoPVTcallbackPtr +// / _____ This _must_ be UBX_NAV_PVT_data_t +// | / _____ You can use any name you like for the struct +// | | / +// | | | +void printPVTdata(UBX_NAV_PVT_data_t *ubxDataStruct) +{ + double latitude = ubxDataStruct->lat; // Print the latitude + Serial.print(F("Lat: ")); + Serial.print(latitude / 10000000.0, 7); + + double longitude = ubxDataStruct->lon; // Print the longitude + Serial.print(F(" Long: ")); + Serial.print(longitude / 10000000.0, 7); + + double altitude = ubxDataStruct->hMSL; // Print the height above mean sea level + Serial.print(F(" Height: ")); + Serial.print(altitude / 1000.0, 3); + + uint8_t fixType = ubxDataStruct->fixType; // Print the fix type + Serial.print(F(" Fix: ")); + Serial.print(fixType); + if (fixType == 0) + Serial.print(F(" (None)")); + else if (fixType == 1) + Serial.print(F(" (Dead Reckoning)")); + else if (fixType == 2) + Serial.print(F(" (2D)")); + else if (fixType == 3) + Serial.print(F(" (3D)")); + else if (fixType == 3) + Serial.print(F(" (GNSS + Dead Reckoning)")); + else if (fixType == 5) + Serial.print(F(" (Time Only)")); + else + Serial.print(F(" (UNKNOWN)")); + + uint8_t carrSoln = ubxDataStruct->flags.bits.carrSoln; // Print the carrier solution + Serial.print(F(" Carrier Solution: ")); + Serial.print(carrSoln); + if (carrSoln == 0) + Serial.print(F(" (None)")); + else if (carrSoln == 1) + Serial.print(F(" (Floating)")); + else if (carrSoln == 2) + Serial.print(F(" (Fixed)")); + else + Serial.print(F(" (UNKNOWN)")); + + uint32_t hAcc = ubxDataStruct->hAcc; // Print the horizontal accuracy estimate + Serial.print(F(" Horizontal Accuracy Estimate: ")); + Serial.print(hAcc); + Serial.print(F(" (mm)")); + + Serial.println(); +} + +//=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + +// Callback: printRXMCOR will be called when new RXM COR data arrives +// See u-blox_structs.h for the full definition of UBX_RXM_COR_data_t +// _____ You can use any name you like for the callback. Use the same name when you call setRXMCORcallbackPtr +// / _____ This _must_ be UBX_RXM_COR_data_t +// | / _____ You can use any name you like for the struct +// | | / +// | | | +void printRXMCOR(UBX_RXM_COR_data_t *ubxDataStruct) +{ + Serial.print(F("UBX-RXM-COR: ebno: ")); + Serial.print(ubxDataStruct->ebno); + + Serial.print(F(" protocol: ")); + if (ubxDataStruct->statusInfo.bits.protocol == 1) + Serial.print(F("RTCM3")); + else if (ubxDataStruct->statusInfo.bits.protocol == 2) + Serial.print(F("SPARTN")); + else if (ubxDataStruct->statusInfo.bits.protocol == 29) + Serial.print(F("PMP (SPARTN)")); + else if (ubxDataStruct->statusInfo.bits.protocol == 30) + Serial.print(F("QZSSL6")); + else + Serial.print(F("Unknown")); + + Serial.print(F(" errStatus: ")); + if (ubxDataStruct->statusInfo.bits.errStatus == 1) + Serial.print(F("Error-free")); + else if (ubxDataStruct->statusInfo.bits.errStatus == 2) + Serial.print(F("Erroneous")); + else + Serial.print(F("Unknown")); + + Serial.print(F(" msgUsed: ")); + if (ubxDataStruct->statusInfo.bits.msgUsed == 1) + Serial.print(F("Not used")); + else if (ubxDataStruct->statusInfo.bits.msgUsed == 2) + Serial.print(F("Used")); + else + Serial.print(F("Unknown")); + + Serial.print(F(" msgEncrypted: ")); + if (ubxDataStruct->statusInfo.bits.msgEncrypted == 1) + Serial.print(F("Not encrypted")); + else if (ubxDataStruct->statusInfo.bits.msgEncrypted == 2) + Serial.print(F("Encrypted")); + else + Serial.print(F("Unknown")); + + Serial.print(F(" msgDecrypted: ")); + if (ubxDataStruct->statusInfo.bits.msgDecrypted == 1) + Serial.print(F("Not decrypted")); + else if (ubxDataStruct->statusInfo.bits.msgDecrypted == 2) + Serial.print(F("Successfully decrypted")); + else + Serial.print(F("Unknown")); + + Serial.println(); +} + +//=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + +void setup() +{ + Serial.begin(115200); + while (!Serial); + Serial.println(F("NEO-D9S SPARTN Corrections")); + + Wire.begin(); //Start I2C + + //=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + // Begin and configure the ZED-F9x + + //myGNSS.enableDebugging(); // Uncomment this line to enable helpful debug messages on Serial + + while (myGNSS.begin() == false) //Connect to the u-blox module using Wire port + { + Serial.println(F("u-blox GNSS module not detected at default I2C address. Please check wiring.")); + delay(2000); + } + Serial.println(F("u-blox GNSS module connected")); + + uint8_t ok = myGNSS.setI2COutput(COM_TYPE_UBX); //Turn off NMEA noise + if (ok) ok = myGNSS.setPortInput(COM_PORT_I2C, COM_TYPE_UBX | COM_TYPE_NMEA | COM_TYPE_SPARTN); //Be sure SPARTN input is enabled + + if (ok) ok = myGNSS.setDGNSSConfiguration(SFE_UBLOX_DGNSS_MODE_FIXED); // Set the differential mode - ambiguities are fixed whenever possible + + if (ok) ok = myGNSS.setNavigationFrequency(1); //Set output in Hz. + + if (ok) ok = myGNSS.setVal8(UBLOX_CFG_SPARTN_USE_SOURCE, 1); // use LBAND PMP message + + if (ok) ok = myGNSS.setVal8(UBLOX_CFG_MSGOUT_UBX_RXM_COR_I2C, 1); // Enable UBX-RXM-COR messages on I2C + + //if (ok) ok = myGNSS.saveConfiguration(VAL_CFG_SUBSEC_IOPORT | VAL_CFG_SUBSEC_MSGCONF); //Optional: Save the ioPort and message settings to NVM + + Serial.print(F("GNSS: configuration ")); + Serial.println(OK(ok)); + + myGNSS.setAutoPVTcallbackPtr(&printPVTdata); // Enable automatic NAV PVT messages with callback to printPVTdata so we can watch the carrier solution go to fixed + + myGNSS.setRXMCORcallbackPtr(&printRXMCOR); // Print the contents of UBX-RXM-COR messages so we can check if the PMP data is being decrypted successfully + + //=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + // Begin and configure the NEO-D9S L-Band receiver + + //myLBand.enableDebugging(); // Uncomment this line to enable helpful debug messages on Serial + + while (myLBand.begin(Wire, 0x43) == false) //Connect to the u-blox NEO-D9S using Wire port. The D9S default I2C address is 0x43 (not 0x42) + { + Serial.println(F("u-blox NEO-D9S not detected at default I2C address. Please check wiring.")); + delay(2000); + } + Serial.println(F("u-blox NEO-D9S connected")); + + ok = myLBand.setVal32(UBLOX_CFG_PMP_CENTER_FREQUENCY, myLBandFreq); // Default 1539812500 Hz + if (ok) ok = myLBand.setVal16(UBLOX_CFG_PMP_SEARCH_WINDOW, 2200); // Default 2200 Hz + if (ok) ok = myLBand.setVal8(UBLOX_CFG_PMP_USE_SERVICE_ID, 0); // Default 1 + if (ok) ok = myLBand.setVal16(UBLOX_CFG_PMP_SERVICE_ID, 21845); // Default 50821 + if (ok) ok = myLBand.setVal16(UBLOX_CFG_PMP_DATA_RATE, 2400); // Default 2400 bps + if (ok) ok = myLBand.setVal8(UBLOX_CFG_PMP_USE_DESCRAMBLER, 1); // Default 1 + if (ok) ok = myLBand.setVal16(UBLOX_CFG_PMP_DESCRAMBLER_INIT, 26969); // Default 23560 + if (ok) ok = myLBand.setVal8(UBLOX_CFG_PMP_USE_PRESCRAMBLING, 0); // Default 0 + if (ok) ok = myLBand.setVal64(UBLOX_CFG_PMP_UNIQUE_WORD, 16238547128276412563ull); + if (ok) ok = myLBand.setVal(UBLOX_CFG_MSGOUT_UBX_RXM_PMP_I2C, 1); // Ensure UBX-RXM-PMP is enabled on the I2C port + if (ok) ok = myLBand.setVal(UBLOX_CFG_MSGOUT_UBX_RXM_PMP_UART1, 1); // Output UBX-RXM-PMP on UART1 + if (ok) ok = myLBand.setVal(UBLOX_CFG_UART2OUTPROT_UBX, 1); // Enable UBX output on UART2 + if (ok) ok = myLBand.setVal(UBLOX_CFG_MSGOUT_UBX_RXM_PMP_UART2, 1); // Output UBX-RXM-PMP on UART2 + if (ok) ok = myLBand.setVal32(UBLOX_CFG_UART1_BAUDRATE, 38400); // match baudrate with ZED default + if (ok) ok = myLBand.setVal32(UBLOX_CFG_UART2_BAUDRATE, 38400); // match baudrate with ZED default + + Serial.print(F("L-Band: configuration ")); + Serial.println(OK(ok)); + + myLBand.softwareResetGNSSOnly(); // Do a restart + + myLBand.setRXMPMPmessageCallbackPtr(&pushRXMPMP); // Call pushRXMPMP when new PMP data arrives. Push it to the GNSS + + //=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + // Connect to WiFi so we can request the dynamic keys via MQTT + + Serial.print(F("Connecting to local WiFi")); + WiFi.begin(ssid, password); + while (WiFi.status() != WL_CONNECTED) { + delay(500); + Serial.print(F(".")); + } + Serial.println(); + + Serial.print(F("WiFi connected with IP: ")); + Serial.println(WiFi.localIP()); + + while (Serial.available()) Serial.read(); + + Serial.println(F("Press any key to start MQTT Client.")); + +} + +void loop() +{ + if (Serial.available()) + { + beginClient(); + + while (Serial.available()) Serial.read(); //Empty buffer of any newline chars + + Serial.println(F("Press any key to start MQTT Client.")); + } + + myGNSS.checkUblox(); // Check for the arrival of new GNSS data and process it. + myGNSS.checkCallbacks(); // Check if any GNSS callbacks are waiting to be processed. + + myLBand.checkUblox(); // Check for the arrival of new PMP data and process it. + myLBand.checkCallbacks(); // Check if any LBand callbacks are waiting to be processed. +} + +WiFiClientSecure wifiClient = WiFiClientSecure(); +MqttClient mqttClient(wifiClient); + +void mqttMessageHandler(int messageSize) +{ + const uint16_t mqttLimit = 512; + uint8_t *mqttData = new uint8_t[mqttLimit]; // Allocate memory to hold the MQTT data + if (mqttData == NULL) + { + Serial.println(F("Memory allocation for mqttData failed!")); + return; + } + + Serial.print(F("Pushing data from ")); + Serial.print(mqttClient.messageTopic()); + Serial.println(F(" topic to ZED")); + + while (mqttClient.available()) + { + uint16_t mqttCount = 0; + + while (mqttClient.available()) + { + char ch = mqttClient.read(); + //Serial.write(ch); //Pipe to serial port is fine but beware, it's a lot of binary data + mqttData[mqttCount++] = ch; + + if (mqttCount == mqttLimit) + break; + } + + if (mqttCount > 0) + { + //Push KEYS or SPARTN data to GNSS module over I2C + myGNSS.pushRawData(mqttData, mqttCount, false); + lastReceived_ms = millis(); + + if ((mqttData[0] == 0xB5) // Check if this is UBX-RXM-SPARTNKEY + && (mqttData[1] == 0x62) + && (mqttData[2] == 0x02) // Class: RXM + && (mqttData[3] == 0x36)) // ID: SPARTNKEY + { + uint8_t numKeys = mqttData[7]; // Get the number of keys + uint8_t keyStart = 10 + (numKeys * 8); // Point to the start of the first key + for (uint8_t key = 0; key < numKeys; key++) + { + Serial.print(F("SPARTNKEY: ")); + Serial.println(key); + Serial.print(F("Valid from GPS week number: ")); + uint16_t validFromWno = ((uint16_t)mqttData[12 + (key * 8)]) | ((uint16_t)mqttData[13 + (key * 8)] << 8); // Little endian + Serial.println(validFromWno); + Serial.print(F("Valid from GPS time of week: ")); + uint32_t validFromTow = ((uint32_t)mqttData[14 + (key * 8)]) | ((uint32_t)mqttData[15 + (key * 8)] << 8) | ((uint32_t)mqttData[16 + (key * 8)] << 16) | ((uint32_t)mqttData[17 + (key * 8)] << 24); + Serial.println(validFromTow); + uint8_t keyLengthBytes = mqttData[11 + (key * 8)]; + Serial.print(F("Key length (bytes): ")); + Serial.println(keyLengthBytes); + Serial.print(F("Key: \"")); + for (uint8_t digit = 0; digit < keyLengthBytes; digit++) + { + Serial.print(mqttData[keyStart + digit] >> 4, HEX); // Print the key as ASCII Hex + Serial.print(mqttData[keyStart + digit] & 0x0F, HEX); // Print the key as ASCII Hex + } + Serial.println(F("\"")); + keyStart += keyLengthBytes; // Update keyStart for the next key + } + } + } + } + + delete[] mqttData; +} + +//Connect to MQTT broker, receive dynamic keys and push to ZED module over I2C +void beginClient() +{ + Serial.println(F("Subscribing to Broker. Press key to stop")); + delay(10); //Wait for any serial to arrive + while (Serial.available()) Serial.read(); //Flush + + while (Serial.available() == 0) + { + //Connect if we are not already + if (wifiClient.connected() == false) + { + // Connect to AWS IoT + wifiClient.setCACert(AWS_CERT_CA); + wifiClient.setCertificate(AWS_CERT_CRT); + wifiClient.setPrivateKey(AWS_CERT_PRIVATE); + mqttClient.setId(MQTT_CLIENT_ID); + mqttClient.setKeepAliveInterval(60*1000); + mqttClient.setConnectionTimeout( 5*1000); + if (!mqttClient.connect(AWS_IOT_ENDPOINT, AWS_IOT_PORT)) { + Serial.print(F("MQTT connection failed! Error code = ")); + Serial.println(mqttClient.connectError()); + return; + } else { + Serial.println(F("You're connected to the PointPerfect MQTT broker: ")); + Serial.println(AWS_IOT_ENDPOINT); + // Subscribe to MQTT and register a callback + Serial.println(F("Subscribe to Topics")); + mqttClient.onMessage(mqttMessageHandler); + mqttClient.subscribe(MQTT_TOPIC_KEY); + lastReceived_ms = millis(); + } //End attempt to connect + } //End connected == false + else { + mqttClient.poll(); + } + + //Close socket if we don't have new data for 10s + if (millis() - lastReceived_ms > maxTimeBeforeHangup_ms) + { + Serial.println(F("MQTT timeout. Disconnecting...")); + if (mqttClient.connected() == true) + mqttClient.stop(); + return; + } + + myGNSS.checkUblox(); // Check for the arrival of new GNSS data and process it. + myGNSS.checkCallbacks(); // Check if any GNSS callbacks are waiting to be processed. + + myLBand.checkUblox(); // Check for the arrival of new PMP data and process it. + myLBand.checkCallbacks(); // Check if any LBand callbacks are waiting to be processed. + + delay(10); + } + + Serial.println(F("User pressed a key")); + Serial.println(F("Disconnecting...")); + wifiClient.stop(); +} diff --git a/examples/ZED-F9P/Example20_PMP_with_L-Band_Keys_via_MQTT/secrets.h b/examples/ZED-F9P/Example20_PMP_with_L-Band_Keys_via_MQTT/secrets.h new file mode 100644 index 0000000..3ce1f34 --- /dev/null +++ b/examples/ZED-F9P/Example20_PMP_with_L-Band_Keys_via_MQTT/secrets.h @@ -0,0 +1,40 @@ +//Your WiFi credentials +const char ssid[] = ""; +const char password[] = ""; + +// Below infomation you can set after signing up with u-blox Thingstream portal +// and after add a new New PointPerfect Thing (L-Band or L-Band + IP) +// https://portal.thingstream.io/app/location-services/things +// in the new PointPerfect Thing you go to the credentials page and copy paste the values and certificate into this. + +// -> Credentials -> Hostname +const char AWS_IOT_ENDPOINT[] = "pp.services.u-blox.com"; +const unsigned short AWS_IOT_PORT = 8883; +// -> Credentials -> IP key distribution topic +//const char MQTT_TOPIC_KEY[] = "/pp/ubx/0236/ip"; // This topic provides the IP only dynamic keys in UBX format +const char MQTT_TOPIC_KEY[] = "/pp/ubx/0236/Lb"; // This topic provides the L-Band + IP dynamic keys in UBX format + +// -> Credentials -> Client Id +static const char MQTT_CLIENT_ID[] = ""; + +// -> Credentials -> Amazon Root Certificate +static const char AWS_CERT_CA[] PROGMEM = R"EOF( +-----BEGIN CERTIFICATE----- + +-----END CERTIFICATE----- +)EOF"; + +// -> Credentials -> Client Certificate +static const char AWS_CERT_CRT[] PROGMEM = R"KEY( +-----BEGIN CERTIFICATE----- + +-----END CERTIFICATE----- +)KEY"; + +// Get this from Thingstream Portal +// -> Credentials -> Client Key +static const char AWS_CERT_PRIVATE[] PROGMEM = R"KEY( +-----BEGIN RSA PRIVATE KEY----- + +-----END RSA PRIVATE KEY----- +)KEY"; diff --git a/keywords.txt b/keywords.txt index 9e34ac0..5e187b2 100644 --- a/keywords.txt +++ b/keywords.txt @@ -40,6 +40,7 @@ UBX_NAV_AOPSTATUS_data_t KEYWORD1 UBX_RXM_PMP_data_t KEYWORD1 UBX_RXM_PMP_message_data_t KEYWORD1 +UBX_RXM_COR_data_t KEYWORD1 UBX_RXM_SFRBX_data_t KEYWORD1 UBX_RXM_RAWX_data_t KEYWORD1 @@ -393,6 +394,8 @@ logAOPSTATUS KEYWORD2 setRXMPMPcallbackPtr KEYWORD2 setRXMPMPmessageCallbackPtr KEYWORD2 +setRXMCORcallbackPtr KEYWORD2 + getRXMSFRBX KEYWORD2 setAutoRXMSFRBX KEYWORD2 setAutoRXMSFRBXrate KEYWORD2 @@ -772,6 +775,8 @@ UBX_NAV_TIMELS LITERAL1 UBX_NAV_VELECEF LITERAL1 UBX_NAV_VELNED LITERAL1 +UBX_RXM_PMP LITERAL1 +UBX_RXM_COR LITERAL1 UBX_RXM_RAWX LITERAL1 UBX_RXM_SFRBX LITERAL1 UBX_RXM_SPARTN LITERAL1 diff --git a/library.properties b/library.properties index 56288d1..19524b0 100644 --- a/library.properties +++ b/library.properties @@ -1,5 +1,5 @@ name=SparkFun u-blox GNSS Arduino Library -version=2.2.6 +version=2.2.7 author=SparkFun Electronics maintainer=SparkFun Electronics sentence=Library for I2C, Serial and SPI 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 dd36b24..1c42dad 100644 --- a/src/SparkFun_u-blox_GNSS_Arduino_Library.cpp +++ b/src/SparkFun_u-blox_GNSS_Arduino_Library.cpp @@ -294,6 +294,16 @@ void SFE_UBLOX_GNSS::end(void) packetUBXRXMPMPmessage = NULL; // Redundant? } + if (packetUBXRXMCOR != NULL) + { + if (packetUBXRXMCOR->callbackData != NULL) + { + delete packetUBXRXMCOR->callbackData; + } + delete packetUBXRXMCOR; + packetUBXRXMCOR = NULL; // Redundant? + } + if (packetUBXRXMSFRBX != NULL) { if (packetUBXRXMSFRBX->callbackData != NULL) @@ -1333,6 +1343,10 @@ bool SFE_UBLOX_GNSS::checkAutomatic(uint8_t Class, uint8_t ID) if ((packetUBXRXMPMP != NULL) || (packetUBXRXMPMPmessage != NULL)) result = true; break; + case UBX_RXM_COR: + if (packetUBXRXMCOR != NULL) + result = true; + break; } } break; @@ -1504,6 +1518,9 @@ uint16_t SFE_UBLOX_GNSS::getMaxPayloadSize(uint8_t Class, uint8_t ID) case UBX_RXM_PMP: maxSize = UBX_RXM_PMP_MAX_LEN; break; + case UBX_RXM_COR: + maxSize = UBX_RXM_COR_LEN; + break; } } break; @@ -3772,6 +3789,22 @@ void SFE_UBLOX_GNSS::processUBXpacket(ubxPacket *msg) packetUBXRXMPMPmessage->automaticFlags.flags.bits.callbackCopyValid = true; // Mark the data as valid } } + else if (msg->id == UBX_RXM_COR) + { + // Parse various byte fields into storage - but only if we have memory allocated for it + if ((packetUBXRXMCOR != NULL) && (packetUBXRXMCOR->callbackData != NULL) + //&& (packetUBXRXMCOR->automaticFlags.flags.bits.callbackCopyValid == false) // <=== Uncomment this line to prevent new data from overwriting 'old' + ) + { + packetUBXRXMCOR->callbackData->version = extractByte(msg, 0); + packetUBXRXMCOR->callbackData->ebno = extractByte(msg, 1); + packetUBXRXMCOR->callbackData->statusInfo.all = extractLong(msg, 4); + packetUBXRXMCOR->callbackData->msgType = extractInt(msg, 8); + packetUBXRXMCOR->callbackData->msgSubType = extractInt(msg, 10); + + packetUBXRXMCOR->automaticFlags.flags.bits.callbackCopyValid = true; // Mark the data as valid + } + } else if (msg->id == UBX_RXM_SFRBX) // Note: length is variable // Note: on protocol version 17: numWords is (0..16) @@ -5359,6 +5392,17 @@ void SFE_UBLOX_GNSS::checkCallbacks(void) packetUBXRXMPMPmessage->automaticFlags.flags.bits.callbackCopyValid = false; // Mark the data as stale } + if ((packetUBXRXMCOR != NULL) // If RAM has been allocated for message storage + && (packetUBXRXMCOR->callbackData != NULL) // If RAM has been allocated for the copy of the data + && (packetUBXRXMCOR->callbackPointerPtr != NULL) // If the pointer to the callback has been defined + && (packetUBXRXMCOR->automaticFlags.flags.bits.callbackCopyValid == true)) // If the copy of the data is valid + { + // if (_printDebug == true) + // _debugSerial->println(F("checkCallbacks: calling callbackPtr for RXM COR")); + packetUBXRXMCOR->callbackPointerPtr(packetUBXRXMCOR->callbackData); // Call the callback + packetUBXRXMCOR->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->automaticFlags.flags.bits.callbackCopyValid == true)) // If the copy of the data is valid @@ -12316,6 +12360,49 @@ bool SFE_UBLOX_GNSS::initPacketUBXRXMPMPmessage() return (true); } +bool SFE_UBLOX_GNSS::setRXMCORcallbackPtr(void (*callbackPointer)(UBX_RXM_COR_data_t *)) +{ + if (packetUBXRXMCOR == NULL) + initPacketUBXRXMCOR(); // Check that RAM has been allocated for the data + if (packetUBXRXMCOR == NULL) // Only attempt this if RAM allocation was successful + return false; + + if (packetUBXRXMCOR->callbackData == NULL) // Check if RAM has been allocated for the callback copy + { + packetUBXRXMCOR->callbackData = new UBX_RXM_COR_data_t; // Allocate RAM for the main struct + } + + if (packetUBXRXMCOR->callbackData == NULL) + { +#ifndef SFE_UBLOX_REDUCED_PROG_MEM + if ((_printDebug == true) || (_printLimitedDebug == true)) // This is important. Print this if doing limited debugging + _debugSerial->println(F("setAutoRXMCORcallbackPtr: RAM alloc failed!")); +#endif + return (false); + } + + packetUBXRXMCOR->callbackPointerPtr = callbackPointer; + return (true); +} + +// PRIVATE: Allocate RAM for packetUBXRXMCOR and initialize it +bool SFE_UBLOX_GNSS::initPacketUBXRXMCOR() +{ + packetUBXRXMCOR = new UBX_RXM_COR_t; // Allocate RAM for the main struct + if (packetUBXRXMCOR == NULL) + { +#ifndef SFE_UBLOX_REDUCED_PROG_MEM + if ((_printDebug == true) || (_printLimitedDebug == true)) // This is important. Print this if doing limited debugging + _debugSerial->println(F("initPacketUBXRXMCOR: RAM alloc failed!")); +#endif + return (false); + } + packetUBXRXMCOR->automaticFlags.flags.all = 0; + packetUBXRXMCOR->callbackPointerPtr = NULL; + packetUBXRXMCOR->callbackData = NULL; + return (true); +} + // ***** RXM SFRBX automatic support bool SFE_UBLOX_GNSS::getRXMSFRBX(uint16_t maxWait) diff --git a/src/SparkFun_u-blox_GNSS_Arduino_Library.h b/src/SparkFun_u-blox_GNSS_Arduino_Library.h index 45c4594..dde932c 100644 --- a/src/SparkFun_u-blox_GNSS_Arduino_Library.h +++ b/src/SparkFun_u-blox_GNSS_Arduino_Library.h @@ -624,20 +624,19 @@ typedef struct const uint32_t SFE_UBLOX_DAYS_FROM_1970_TO_2020 = 18262; // Jan 1st 2020 Epoch = 1577836800 seconds const uint16_t SFE_UBLOX_DAYS_SINCE_2020[80] = -{ - 0, 366, 731, 1096, 1461, 1827, 2192, 2557, 2922, 3288, - 3653, 4018, 4383, 4749, 5114, 5479, 5844, 6210, 6575, 6940, - 7305, 7671, 8036, 8401, 8766, 9132, 9497, 9862, 10227, 10593, - 10958, 11323, 11688, 12054, 12419, 12784, 13149, 13515, 13880, 14245, - 14610, 14976, 15341, 15706, 16071, 16437, 16802, 17167, 17532, 17898, - 18263, 18628, 18993, 19359, 19724, 20089, 20454, 20820, 21185, 21550, - 21915, 22281, 22646, 23011, 23376, 23742, 24107, 24472, 24837, 25203, - 25568, 25933, 26298, 26664, 27029, 27394, 27759, 28125, 28490, 28855 -}; + { + 0, 366, 731, 1096, 1461, 1827, 2192, 2557, 2922, 3288, + 3653, 4018, 4383, 4749, 5114, 5479, 5844, 6210, 6575, 6940, + 7305, 7671, 8036, 8401, 8766, 9132, 9497, 9862, 10227, 10593, + 10958, 11323, 11688, 12054, 12419, 12784, 13149, 13515, 13880, 14245, + 14610, 14976, 15341, 15706, 16071, 16437, 16802, 17167, 17532, 17898, + 18263, 18628, 18993, 19359, 19724, 20089, 20454, 20820, 21185, 21550, + 21915, 22281, 22646, 23011, 23376, 23742, 24107, 24472, 24837, 25203, + 25568, 25933, 26298, 26664, 27029, 27394, 27759, 28125, 28490, 28855}; const uint16_t SFE_UBLOX_DAYS_SINCE_MONTH[2][12] = -{ - {0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335}, // Leap Year (Year % 4 == 0) - {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334} // Normal Year + { + {0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335}, // Leap Year (Year % 4 == 0) + {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334} // Normal Year }; class SFE_UBLOX_GNSS @@ -860,18 +859,17 @@ class SFE_UBLOX_GNSS bool disableRTCMmessage(uint8_t messageNumber, uint8_t portID, uint16_t maxWait = defaultMaxWait); // Turn off given RTCM message from a given port // Functions used for RTK and base station setup - // It is probably safe to assume that users of the RTK will be using I2C / Qwiic. So let's leave maxWait set to 250ms. - bool getSurveyMode(uint16_t maxWait = 250); // Get the current TimeMode3 settings - bool setSurveyMode(uint8_t mode, uint16_t observationTime, float requiredAccuracy, uint16_t maxWait = 250); // Control survey in mode - bool setSurveyModeFull(uint8_t mode, uint32_t observationTime, float requiredAccuracy, uint16_t maxWait = 250); // Control survey in mode - bool enableSurveyMode(uint16_t observationTime, float requiredAccuracy, uint16_t maxWait = 250); // Begin Survey-In for NEO-M8P / ZED-F9x - bool enableSurveyModeFull(uint32_t observationTime, float requiredAccuracy, uint16_t maxWait = 250); // Begin Survey-In for NEO-M8P / ZED-F9x - bool disableSurveyMode(uint16_t maxWait = 250); // Stop Survey-In mode + bool getSurveyMode(uint16_t maxWait = defaultMaxWait); // Get the current TimeMode3 settings + bool setSurveyMode(uint8_t mode, uint16_t observationTime, float requiredAccuracy, uint16_t maxWait = defaultMaxWait); // Control survey in mode + bool setSurveyModeFull(uint8_t mode, uint32_t observationTime, float requiredAccuracy, uint16_t maxWait = defaultMaxWait); // Control survey in mode + bool enableSurveyMode(uint16_t observationTime, float requiredAccuracy, uint16_t maxWait = defaultMaxWait); // Begin Survey-In for NEO-M8P / ZED-F9x + bool enableSurveyModeFull(uint32_t observationTime, float requiredAccuracy, uint16_t maxWait = defaultMaxWait); // Begin Survey-In for NEO-M8P / ZED-F9x + bool disableSurveyMode(uint16_t maxWait = defaultMaxWait); // Stop Survey-In mode // Given coordinates, put receiver into static position. Set latlong to true to pass in lat/long values instead of ecef. // For ECEF the units are: cm, 0.1mm, cm, 0.1mm, cm, 0.1mm // For Lat/Lon/Alt the units are: degrees^-7, degrees^-9, degrees^-7, degrees^-9, cm, 0.1mm - bool setStaticPosition(int32_t ecefXOrLat, int8_t ecefXOrLatHP, int32_t ecefYOrLon, int8_t ecefYOrLonHP, int32_t ecefZOrAlt, int8_t ecefZOrAltHP, bool latLong = false, uint16_t maxWait = 250); - bool setStaticPosition(int32_t ecefXOrLat, int32_t ecefYOrLon, int32_t ecefZOrAlt, bool latLong = false, uint16_t maxWait = 250); + bool setStaticPosition(int32_t ecefXOrLat, int8_t ecefXOrLatHP, int32_t ecefYOrLon, int8_t ecefYOrLonHP, int32_t ecefZOrAlt, int8_t ecefZOrAltHP, bool latLong = false, uint16_t maxWait = defaultMaxWait); + bool setStaticPosition(int32_t ecefXOrLat, int32_t ecefYOrLon, int32_t ecefZOrAlt, bool latLong = false, uint16_t maxWait = defaultMaxWait); bool setDGNSSConfiguration(sfe_ublox_dgnss_mode_e dgnssMode = SFE_UBLOX_DGNSS_MODE_FIXED, uint16_t maxWait = defaultMaxWait); // Set the DGNSS differential mode // Read the module's protocol version @@ -954,35 +952,33 @@ class SFE_UBLOX_GNSS // 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. - // If they are using Serial then the higher baud rate will also help. So let's leave maxWait set to 250ms. - uint32_t createKey(uint16_t group, uint16_t id, uint8_t size); // Form 32-bit key from group/id/size - sfe_ublox_status_e getVal(uint32_t keyID, uint8_t layer = VAL_LAYER_RAM, uint16_t maxWait = 250); // Load payload with response - uint8_t getVal8(uint32_t keyID, uint8_t layer = VAL_LAYER_RAM, uint16_t maxWait = 250); // Returns the value at a given key location - uint16_t getVal16(uint32_t keyID, uint8_t layer = VAL_LAYER_RAM, uint16_t maxWait = 250); // Returns the value at a given key location - uint32_t getVal32(uint32_t keyID, uint8_t layer = VAL_LAYER_RAM, uint16_t maxWait = 250); // Returns the value at a given key location - uint64_t getVal64(uint32_t keyID, uint8_t layer = VAL_LAYER_RAM, uint16_t maxWait = 250); // Returns the value at a given key location - uint8_t getVal8(uint16_t group, uint16_t id, uint8_t size, uint8_t layer = VAL_LAYER_RAM, uint16_t maxWait = 250); // Returns the value at a given group/id/size location - uint16_t getVal16(uint16_t group, uint16_t id, uint8_t size, uint8_t layer = VAL_LAYER_RAM, uint16_t maxWait = 250); // Returns the value at a given group/id/size location - uint32_t getVal32(uint16_t group, uint16_t id, uint8_t size, uint8_t layer = VAL_LAYER_RAM, uint16_t maxWait = 250); // Returns the value at a given group/id/size location - uint64_t getVal64(uint16_t group, uint16_t id, uint8_t size, uint8_t layer = VAL_LAYER_RAM, uint16_t maxWait = 250); // Returns the value at a given group/id/size location - uint8_t setVal(uint32_t keyID, uint16_t value, uint8_t layer = VAL_LAYER_ALL, uint16_t maxWait = 250); // Sets the 16-bit value at a given group/id/size location - uint8_t setVal8(uint32_t keyID, uint8_t value, uint8_t layer = VAL_LAYER_ALL, uint16_t maxWait = 250); // Sets the 8-bit value at a given group/id/size location - uint8_t setVal16(uint32_t keyID, uint16_t value, uint8_t layer = VAL_LAYER_ALL, uint16_t maxWait = 250); // Sets the 16-bit value at a given group/id/size location - uint8_t setVal32(uint32_t keyID, uint32_t value, uint8_t layer = VAL_LAYER_ALL, uint16_t maxWait = 250); // Sets the 32-bit value at a given group/id/size location - uint8_t setVal64(uint32_t keyID, uint64_t value, uint8_t layer = VAL_LAYER_ALL, uint16_t maxWait = 250); // Sets the 64-bit value at a given group/id/size location - uint8_t newCfgValset8(uint32_t keyID, uint8_t value, uint8_t layer = VAL_LAYER_ALL); // Define a new UBX-CFG-VALSET with the given KeyID and 8-bit value - uint8_t newCfgValset16(uint32_t keyID, uint16_t value, uint8_t layer = VAL_LAYER_ALL); // Define a new UBX-CFG-VALSET with the given KeyID and 16-bit value - uint8_t newCfgValset32(uint32_t keyID, uint32_t value, uint8_t layer = VAL_LAYER_ALL); // Define a new UBX-CFG-VALSET with the given KeyID and 32-bit value - uint8_t newCfgValset64(uint32_t keyID, uint64_t value, uint8_t layer = VAL_LAYER_ALL); // Define a new UBX-CFG-VALSET with the given KeyID and 64-bit value - uint8_t addCfgValset8(uint32_t keyID, uint8_t value); // Add a new KeyID and 8-bit value to an existing UBX-CFG-VALSET ubxPacket - uint8_t addCfgValset16(uint32_t keyID, uint16_t value); // Add a new KeyID and 16-bit value to an existing UBX-CFG-VALSET ubxPacket - uint8_t addCfgValset32(uint32_t keyID, uint32_t value); // Add a new KeyID and 32-bit value to an existing UBX-CFG-VALSET ubxPacket - uint8_t addCfgValset64(uint32_t keyID, uint64_t value); // Add a new KeyID and 64-bit value to an existing UBX-CFG-VALSET ubxPacket - uint8_t sendCfgValset8(uint32_t keyID, uint8_t value, uint16_t maxWait = 250); // Add the final KeyID and 8-bit value to an existing UBX-CFG-VALSET ubxPacket and send it - 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 - uint8_t sendCfgValset64(uint32_t keyID, uint64_t value, uint16_t maxWait = 250); // Add the final KeyID and 64-bit value to an existing UBX-CFG-VALSET ubxPacket and send it + uint32_t createKey(uint16_t group, uint16_t id, uint8_t size); // Form 32-bit key from group/id/size + sfe_ublox_status_e getVal(uint32_t keyID, uint8_t layer = VAL_LAYER_RAM, uint16_t maxWait = defaultMaxWait); // Load payload with response + uint8_t getVal8(uint32_t keyID, uint8_t layer = VAL_LAYER_RAM, uint16_t maxWait = defaultMaxWait); // Returns the value at a given key location + uint16_t getVal16(uint32_t keyID, uint8_t layer = VAL_LAYER_RAM, uint16_t maxWait = defaultMaxWait); // Returns the value at a given key location + uint32_t getVal32(uint32_t keyID, uint8_t layer = VAL_LAYER_RAM, uint16_t maxWait = defaultMaxWait); // Returns the value at a given key location + uint64_t getVal64(uint32_t keyID, uint8_t layer = VAL_LAYER_RAM, uint16_t maxWait = defaultMaxWait); // Returns the value at a given key location + uint8_t getVal8(uint16_t group, uint16_t id, uint8_t size, uint8_t layer = VAL_LAYER_RAM, uint16_t maxWait = defaultMaxWait); // Returns the value at a given group/id/size location + uint16_t getVal16(uint16_t group, uint16_t id, uint8_t size, uint8_t layer = VAL_LAYER_RAM, uint16_t maxWait = defaultMaxWait); // Returns the value at a given group/id/size location + uint32_t getVal32(uint16_t group, uint16_t id, uint8_t size, uint8_t layer = VAL_LAYER_RAM, uint16_t maxWait = defaultMaxWait); // Returns the value at a given group/id/size location + uint64_t getVal64(uint16_t group, uint16_t id, uint8_t size, uint8_t layer = VAL_LAYER_RAM, uint16_t maxWait = defaultMaxWait); // Returns the value at a given group/id/size location + uint8_t setVal(uint32_t keyID, uint16_t value, uint8_t layer = VAL_LAYER_ALL, uint16_t maxWait = defaultMaxWait); // Sets the 16-bit value at a given group/id/size location + uint8_t setVal8(uint32_t keyID, uint8_t value, uint8_t layer = VAL_LAYER_ALL, uint16_t maxWait = defaultMaxWait); // Sets the 8-bit value at a given group/id/size location + uint8_t setVal16(uint32_t keyID, uint16_t value, uint8_t layer = VAL_LAYER_ALL, uint16_t maxWait = defaultMaxWait); // Sets the 16-bit value at a given group/id/size location + uint8_t setVal32(uint32_t keyID, uint32_t value, uint8_t layer = VAL_LAYER_ALL, uint16_t maxWait = defaultMaxWait); // Sets the 32-bit value at a given group/id/size location + uint8_t setVal64(uint32_t keyID, uint64_t value, uint8_t layer = VAL_LAYER_ALL, uint16_t maxWait = defaultMaxWait); // Sets the 64-bit value at a given group/id/size location + uint8_t newCfgValset8(uint32_t keyID, uint8_t value, uint8_t layer = VAL_LAYER_ALL); // Define a new UBX-CFG-VALSET with the given KeyID and 8-bit value + uint8_t newCfgValset16(uint32_t keyID, uint16_t value, uint8_t layer = VAL_LAYER_ALL); // Define a new UBX-CFG-VALSET with the given KeyID and 16-bit value + uint8_t newCfgValset32(uint32_t keyID, uint32_t value, uint8_t layer = VAL_LAYER_ALL); // Define a new UBX-CFG-VALSET with the given KeyID and 32-bit value + uint8_t newCfgValset64(uint32_t keyID, uint64_t value, uint8_t layer = VAL_LAYER_ALL); // Define a new UBX-CFG-VALSET with the given KeyID and 64-bit value + uint8_t addCfgValset8(uint32_t keyID, uint8_t value); // Add a new KeyID and 8-bit value to an existing UBX-CFG-VALSET ubxPacket + uint8_t addCfgValset16(uint32_t keyID, uint16_t value); // Add a new KeyID and 16-bit value to an existing UBX-CFG-VALSET ubxPacket + uint8_t addCfgValset32(uint32_t keyID, uint32_t value); // Add a new KeyID and 32-bit value to an existing UBX-CFG-VALSET ubxPacket + uint8_t addCfgValset64(uint32_t keyID, uint64_t value); // Add a new KeyID and 64-bit value to an existing UBX-CFG-VALSET ubxPacket + uint8_t sendCfgValset8(uint32_t keyID, uint8_t value, uint16_t maxWait = defaultMaxWait); // Add the final KeyID and 8-bit value to an existing UBX-CFG-VALSET ubxPacket and send it + uint8_t sendCfgValset16(uint32_t keyID, uint16_t value, uint16_t maxWait = defaultMaxWait); // 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 = defaultMaxWait); // Add the final KeyID and 32-bit value to an existing UBX-CFG-VALSET ubxPacket and send it + uint8_t sendCfgValset64(uint32_t keyID, uint64_t value, uint16_t maxWait = defaultMaxWait); // Add the final KeyID and 64-bit value to an existing UBX-CFG-VALSET ubxPacket and send it // get and set functions for all of the "automatic" message processing @@ -1159,6 +1155,8 @@ class SFE_UBLOX_GNSS bool setRXMPMPcallbackPtr(void (*callbackPointerPtr)(UBX_RXM_PMP_data_t *)); // Callback receives a pointer to the data, instead of _all_ the data. Much kinder on the stack! bool setRXMPMPmessageCallbackPtr(void (*callbackPointerPtr)(UBX_RXM_PMP_message_data_t *)); // Use this if you want all of the PMP message (including sync chars, checksum, etc.) to push to a GNSS + bool setRXMCORcallbackPtr(void (*callbackPointerPtr)(UBX_RXM_COR_data_t *)); // RXM COR + bool getRXMSFRBX(uint16_t maxWait = defaultMaxWait); // RXM SFRBX bool setAutoRXMSFRBX(bool enabled, uint16_t maxWait = defaultMaxWait); // Enable/disable automatic RXM SFRBX reports at the navigation frequency bool setAutoRXMSFRBX(bool enabled, bool implicitUpdate, uint16_t maxWait = defaultMaxWait); // Enable/disable automatic RXM SFRBX 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 @@ -1512,6 +1510,7 @@ class SFE_UBLOX_GNSS UBX_RXM_PMP_t *packetUBXRXMPMP = NULL; // Pointer to struct. RAM will be allocated for this if/when necessary UBX_RXM_PMP_message_t *packetUBXRXMPMPmessage = NULL; // Pointer to struct. RAM will be allocated for this if/when necessary + UBX_RXM_COR_t *packetUBXRXMCOR = 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 @@ -1610,6 +1609,7 @@ class SFE_UBLOX_GNSS bool initPacketUBXNAVAOPSTATUS(); // Allocate RAM for packetUBXNAVAOPSTATUS and initialize it bool initPacketUBXRXMPMP(); // Allocate RAM for packetUBXRXMPMP and initialize it bool initPacketUBXRXMPMPmessage(); // Allocate RAM for packetUBXRXMPMPRaw and initialize it + bool initPacketUBXRXMCOR(); // Allocate RAM for packetUBXRXMCOR and initialize it bool initPacketUBXRXMSFRBX(); // Allocate RAM for packetUBXRXMSFRBX and initialize it bool initPacketUBXRXMRAWX(); // Allocate RAM for packetUBXRXMRAWX and initialize it bool initPacketUBXCFGPRT(); // Allocate RAM for packetUBXCFGPRT and initialize it diff --git a/src/u-blox_structs.h b/src/u-blox_structs.h index 3bef638..321bf53 100644 --- a/src/u-blox_structs.h +++ b/src/u-blox_structs.h @@ -1484,6 +1484,66 @@ typedef struct UBX_RXM_RAWX_data_t *callbackData; } UBX_RXM_RAWX_t; +// UBX-RXM-COR (0x02 0x34): Differential correction input status +const uint16_t UBX_RXM_COR_LEN = 12; + +typedef struct +{ + uint8_t version; // Message version (0x01 for this version) + uint8_t ebno; // Energy per bit to noise power spectral density ratio (Eb/N0): 2^-3 dB + // 0: unknown. Reported only for protocol UBX-RXM-PMP (SPARTN) to monitor signal quality. + uint8_t reserved0[2]; // Reserved + union + { + uint32_t all; + struct + { + uint32_t protocol : 5; // Input correction data protocol: + // 0: Unknown + // 1: RTCM3 + // 2: SPARTN (Secure Position Augmentation for Real Time Navigation) + // 29: UBX-RXM-PMP (SPARTN) + // 30: UBX-RXM-QZSSL6 + uint32_t errStatus : 2; // Error status of the received correction message content based on possibly available error codes or checksums: + // 0: Unknown + // 1: Error-free + // 2: Erroneous + uint32_t msgUsed : 2; // Status of receiver using the input message: + // 0: Unknown + // 1: Not used + // 2: Used + uint32_t correctionId : 16; // Identifier for the correction stream: + // For RTCM 3: Reference station ID (DF003) of the received RTCM input message. + // Valid range 0-4095. + // For all other messages, reports 0xFFFF. + // For other correction protocols 0xFFFF. + uint32_t msgTypeValid : 1; // Validity of the msgType field. Set to False e.g. if the protocol does not define msgType. + uint32_t msgSubTypeValid : 1; // Validity of the msgSubType field. Set to False e.g. if the protocol does not define subtype for the msgType. + uint32_t msgInputHandle : 1; // Input handling support of the input message: + // 0: Receiver does not have input handling support for this message + // 1: Receiver has input handling support for this message + uint32_t msgEncrypted : 2; // Encryption status of the input message: + // 0: Unknown + // 1: Not encrypted + // 2: Encrypted + uint32_t msgDecrypted : 2; // Decryption status of the input message: + // 0: Unknown + // 1: Not decrypted + // 2: Successfully decrypted + } bits; + } statusInfo; + uint16_t msgType; // Message type + uint16_t msgSubType; // Message subtype +} UBX_RXM_COR_data_t; + +// The COR data can only be accessed via a callback. COR cannot be polled. +typedef struct +{ + ubxAutomaticFlags automaticFlags; + void (*callbackPointerPtr)(UBX_RXM_COR_data_t *); + UBX_RXM_COR_data_t *callbackData; +} UBX_RXM_COR_t; + // UBX-RXM-PMP (0x02 0x72): PMP raw data (D9 modules) // There are two versions of this message but, fortunately, both have a max len of 528 const uint16_t UBX_RXM_PMP_MAX_USER_DATA = 504; @@ -1702,36 +1762,36 @@ const uint16_t UBX_MON_HW_LEN = 60; typedef struct { - uint32_t pinSel; // Mask of pins set as peripheral/PIO - uint32_t pinBank; // Mask of pins set as bank A/B - uint32_t pinDir; // Mask of pins set as input/output - uint32_t pinVal; // Mask of pins value low/high + uint32_t pinSel; // Mask of pins set as peripheral/PIO + uint32_t pinBank; // Mask of pins set as bank A/B + uint32_t pinDir; // Mask of pins set as input/output + uint32_t pinVal; // Mask of pins value low/high uint16_t noisePerMS; // Noise level as measured by the GPS core - uint16_t agcCnt; // AGC monitor (counts SIGHI xor SIGLO, range 0 to 8191) - uint8_t aStatus; // Status of the antenna supervisor state machine (0=INIT, 1=DONTKNOW, 2=OK, 3=SHORT, 4=OPEN) - uint8_t aPower; // Current power status of antenna (0=OFF, 1=ON, 2=DONTKNOW) + uint16_t agcCnt; // AGC monitor (counts SIGHI xor SIGLO, range 0 to 8191) + uint8_t aStatus; // Status of the antenna supervisor state machine (0=INIT, 1=DONTKNOW, 2=OK, 3=SHORT, 4=OPEN) + uint8_t aPower; // Current power status of antenna (0=OFF, 1=ON, 2=DONTKNOW) union { uint8_t all; struct { - uint8_t rtcCalib : 1; // RTC is calibrated - uint8_t safeBoot : 1; // Safeboot mode (0 = inactive, 1 = active) + uint8_t rtcCalib : 1; // RTC is calibrated + uint8_t safeBoot : 1; // Safeboot mode (0 = inactive, 1 = active) uint8_t jammingState : 2; // Output from jamming/interference monitor (0 = unknown or feature disabled, // 1 = ok - no significant jamming, // 2 = warning - interference visible but fix OK, // 3 = critical - interference visible and no fix) - uint8_t xtalAbsent : 1; // RTC xtal has been determined to be absent + uint8_t xtalAbsent : 1; // RTC xtal has been determined to be absent } bits; } flags; - uint8_t reserved1; // Reserved - uint32_t usedMask; // Mask of pins that are used by the virtual pin manager - uint8_t VP[17]; // Array of pin mappings for each of the 17 physical pins - uint8_t jamInd; // CW jamming indicator, scaled (0 = no CW jamming, 255 = strong CW jamming) + uint8_t reserved1; // Reserved + uint32_t usedMask; // Mask of pins that are used by the virtual pin manager + uint8_t VP[17]; // Array of pin mappings for each of the 17 physical pins + uint8_t jamInd; // CW jamming indicator, scaled (0 = no CW jamming, 255 = strong CW jamming) uint8_t reserved2[2]; // Reserved - uint32_t pinIrq; // Mask of pins value using the PIO Irq - uint32_t pullH; // Mask of pins value using the PIO pull high resistor - uint8_t pullL; // Mask of pins value using the PIO pull low resistor + uint32_t pinIrq; // Mask of pins value using the PIO Irq + uint32_t pullH; // Mask of pins value using the PIO pull high resistor + uint8_t pullL; // Mask of pins value using the PIO pull low resistor } UBX_MON_HW_data_t; // UBX-MON-HW2 (0x0A 0x0B): Extended hardware status @@ -1739,15 +1799,15 @@ const uint16_t UBX_MON_HW2_LEN = 28; typedef struct { - int8_t ofsI; // Imbalance of I-part of complex signal, scaled (-128 = max. negative imbalance, 127 = max. positive imbalance) - uint8_t magI; // Magnitude of I-part of complex signal, scaled (0 = no signal, 255 = max. magnitude) - int8_t ofsQ; // Imbalance of Q-part of complex signal, scaled (-128 = max. negative imbalance, 127 = max. positive imbalance) - uint8_t magQ; // Magnitude of Q-part of complex signal, scaled (0 = no signal, 255 = max. magnitude) + int8_t ofsI; // Imbalance of I-part of complex signal, scaled (-128 = max. negative imbalance, 127 = max. positive imbalance) + uint8_t magI; // Magnitude of I-part of complex signal, scaled (0 = no signal, 255 = max. magnitude) + int8_t ofsQ; // Imbalance of Q-part of complex signal, scaled (-128 = max. negative imbalance, 127 = max. positive imbalance) + uint8_t magQ; // Magnitude of Q-part of complex signal, scaled (0 = no signal, 255 = max. magnitude) uint8_t cfgSource; // Source of low-level configuration (114 = ROM, 111 = OTP, 112 = config pins, 102 = flash image) uint8_t reserved0[3]; uint32_t lowLevCfg; // Low-level configuration (obsolete for protocol versions greater than 15.00) uint8_t reserved1[8]; - uint32_t postStatus; // POST status word + uint32_t postStatus; // POST status word uint8_t reserved2[4]; // Reserved } UBX_MON_HW2_data_t;