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 72453b3..5fb0a4a 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,6 +10,9 @@ 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). + Copy and paste the Current Key and Next Key into secrets.h. + Feel like supporting open source hardware? Buy a board from SparkFun! ZED-F9P RTK2: https://www.sparkfun.com/products/16481 @@ -21,6 +24,8 @@ Open the serial monitor at 115200 baud to see the output */ +#include "secrets.h" // <- Copy and paste the Current Key and Next Key into secrets.h + #include //http://librarymanager/All#SparkFun_u-blox_GNSS SFE_UBLOX_GNSS myGNSS; // ZED-F9x SFE_UBLOX_GNSS myLBand; // NEO-D9S @@ -141,17 +146,28 @@ void setup() } Serial.println(F("u-blox GNSS 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 + 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 - myGNSS.setDGNSSConfiguration(SFE_UBLOX_DGNSS_MODE_FIXED); // Set the differential mode - ambiguities are fixed whenever possible + if (ok) ok = myGNSS.setDGNSSConfiguration(SFE_UBLOX_DGNSS_MODE_FIXED); // Set the differential mode - ambiguities are fixed whenever possible - myGNSS.setNavigationFrequency(1); //Set output in Hz. + if (ok) ok = myGNSS.setNavigationFrequency(1); //Set output in Hz. + + //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. + //"Every time the 'current' key is expired, 'next' takes its place." + //"Therefore the host should then retrieve the new 'next' key and send only that." - Use setDynamicSPARTNKey for this. + // The key can be provided in binary format or in ASCII Hex format, but in both cases keyLengthBytes _must_ represent the binary key length in bytes. + if (ok) ok = myGNSS.setDynamicSPARTNKeys(currentKeyLengthBytes, currentKeyGPSWeek, currentKeyGPSToW, currentDynamicKey, + nextKeyLengthBytes, nextKeyGPSWeek, nextKeyGPSToW, nextDynamicKey); + + //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.saveConfiguration(VAL_CFG_SUBSEC_IOPORT | VAL_CFG_SUBSEC_MSGCONF); //Optional: Save the ioPort and message settings to NVM - //=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= // Begin and configure the NEO-D9S L-Band receiver @@ -164,7 +180,7 @@ void setup() } Serial.println(F("u-blox NEO-D9S connected")); - uint8_t ok = myLBand.setVal32(UBLOX_CFG_PMP_CENTER_FREQUENCY, myLBandFreq); // Default 1539812500 Hz + 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 @@ -178,6 +194,7 @@ void setup() 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)); 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 new file mode 100644 index 0000000..b205c9a --- /dev/null +++ b/examples/ZED-F9P/Example19_LBand_Corrections_with_NEO-D9S/secrets.h @@ -0,0 +1,24 @@ +// You can set the information below after signing up with the u-blox Thingstream portal +// and adding a new New PointPerfect Thing +// 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. +// 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 + +const uint8_t currentKeyLengthBytes = 16; +const uint8_t currentDynamicKey[] = "f742bd6b7248043177dd649141d8fb0b"; +const uint16_t currentKeyGPSWeek = 2192; +const uint32_t currentKeyGPSToW = 518418; + +const uint8_t nextKeyLengthBytes = 16; +const uint8_t nextDynamicKey[] = "8206........................29f4"; +const uint16_t nextKeyGPSWeek = 2196; +const uint32_t nextKeyGPSToW = 518418; diff --git a/src/SparkFun_u-blox_GNSS_Arduino_Library.cpp b/src/SparkFun_u-blox_GNSS_Arduino_Library.cpp index 6789cc3..0bfa29a 100644 --- a/src/SparkFun_u-blox_GNSS_Arduino_Library.cpp +++ b/src/SparkFun_u-blox_GNSS_Arduino_Library.cpp @@ -7585,27 +7585,6 @@ bool SFE_UBLOX_GNSS::setAopCfg(uint8_t aopCfg, uint16_t aopOrbMaxErr, uint16_t m // The key can be provided in binary format or in ASCII Hex format, but in both cases keyLengthBytes _must_ represent the binary key length in bytes. bool SFE_UBLOX_GNSS::setDynamicSPARTNKey(uint8_t keyLengthBytes, uint16_t validFromWno, uint32_t validFromTow, const uint8_t *key, uint16_t maxWait) { - // Check if all keyLengthBytes are ASCII Hex 0-9, a-f, A-F - bool isASCIIHex = true; - uint16_t i = 0; - while ((i < (uint16_t)keyLengthBytes) && (isASCIIHex == true)) - { - if (((key[i] >= '0') && (key[i] <= '9')) || ((key[i] >= 'a') && (key[i] <= 'f')) || ((key[i] >= 'A') && (key[i] <= 'F'))) - i++; // Keep checking if data is all ASCII Hex - else - isASCIIHex = false; // Data is binary - } - if (isASCIIHex) // Check the second half of the ASCII Hex key - { - while ((i < ((uint16_t)keyLengthBytes * 2) && (isASCIIHex == true))) - { - if (((key[i] >= '0') && (key[i] <= '9')) || ((key[i] >= 'a') && (key[i] <= 'f')) || ((key[i] >= 'A') && (key[i] <= 'F'))) - i++; // Keep checking if data is all ASCII Hex - else - isASCIIHex = false; // Data is binary - } - } - // Check if there is room for the key in packetCfg. Resize the buffer if not. size_t payloadLength = (size_t)keyLengthBytes + 12; if (packetCfgPayloadSize < payloadLength) @@ -7635,6 +7614,27 @@ bool SFE_UBLOX_GNSS::setDynamicSPARTNKey(uint8_t keyLengthBytes, uint16_t validF payloadCfg[10] = (validFromTow >> 16) & 0xFF; payloadCfg[11] = (validFromTow >> 24) & 0xFF; + // Check if all keyLengthBytes are ASCII Hex 0-9, a-f, A-F + bool isASCIIHex = true; + uint16_t i = 0; + while ((i < (uint16_t)keyLengthBytes) && (isASCIIHex == true)) + { + if (((key[i] >= '0') && (key[i] <= '9')) || ((key[i] >= 'a') && (key[i] <= 'f')) || ((key[i] >= 'A') && (key[i] <= 'F'))) + i++; // Keep checking if data is all ASCII Hex + else + isASCIIHex = false; // Data is binary + } + if (isASCIIHex) // Check the second half of the ASCII Hex key + { + while ((i < ((uint16_t)keyLengthBytes * 2) && (isASCIIHex == true))) + { + if (((key[i] >= '0') && (key[i] <= '9')) || ((key[i] >= 'a') && (key[i] <= 'f')) || ((key[i] >= 'A') && (key[i] <= 'F'))) + i++; // Keep checking if data is all ASCII Hex + else + isASCIIHex = false; // Data is binary + } + } + if (isASCIIHex) // Convert ASCII Hex key to binary { for (i = 0; i < ((uint16_t)keyLengthBytes * 2); i += 2) @@ -7677,6 +7677,157 @@ bool SFE_UBLOX_GNSS::setDynamicSPARTNKey(uint8_t keyLengthBytes, uint16_t validF bool SFE_UBLOX_GNSS::setDynamicSPARTNKeys(uint8_t keyLengthBytes1, uint16_t validFromWno1, uint32_t validFromTow1, const uint8_t *key1, uint8_t keyLengthBytes2, uint16_t validFromWno2, uint32_t validFromTow2, const uint8_t *key2, uint16_t maxWait) { + // Check if there is room for the key in packetCfg. Resize the buffer if not. + size_t payloadLength = (size_t)keyLengthBytes1 + (size_t)keyLengthBytes2 + 20; + if (packetCfgPayloadSize < payloadLength) + { + if (!setPacketCfgPayloadSize(payloadLength)) // Check if the resize was successful + { + return (false); + } + } + + // Copy the key etc. into packetCfg + packetCfg.cls = UBX_CLASS_RXM; + packetCfg.id = UBX_RXM_SPARTNKEY; + packetCfg.len = payloadLength; + packetCfg.startingSpot = 0; + + payloadCfg[0] = 0x01; // version + payloadCfg[1] = 0x02; // numKeys + payloadCfg[2] = 0x00; // reserved0 + payloadCfg[3] = 0x00; // reserved0 + payloadCfg[4] = 0x00; // reserved1 + payloadCfg[5] = keyLengthBytes1; + payloadCfg[6] = validFromWno1 & 0xFF; // validFromWno little-endian + payloadCfg[7] = validFromWno1 >> 8; + payloadCfg[8] = validFromTow1 & 0xFF; // validFromTow little-endian + payloadCfg[9] = (validFromTow1 >> 8) & 0xFF; + payloadCfg[10] = (validFromTow1 >> 16) & 0xFF; + payloadCfg[11] = (validFromTow1 >> 24) & 0xFF; + payloadCfg[12] = 0x00; // reserved1 + payloadCfg[13] = keyLengthBytes2; + payloadCfg[14] = validFromWno2 & 0xFF; // validFromWno little-endian + payloadCfg[15] = validFromWno2 >> 8; + payloadCfg[16] = validFromTow2 & 0xFF; // validFromTow little-endian + payloadCfg[17] = (validFromTow2 >> 8) & 0xFF; + payloadCfg[18] = (validFromTow2 >> 16) & 0xFF; + payloadCfg[19] = (validFromTow2 >> 24) & 0xFF; + + // Check if all keyLengthBytes are ASCII Hex 0-9, a-f, A-F + bool isASCIIHex = true; + uint16_t i = 0; + while ((i < (uint16_t)keyLengthBytes1) && (isASCIIHex == true)) + { + if (((key1[i] >= '0') && (key1[i] <= '9')) || ((key1[i] >= 'a') && (key1[i] <= 'f')) || ((key1[i] >= 'A') && (key1[i] <= 'F'))) + i++; // Keep checking if data is all ASCII Hex + else + isASCIIHex = false; // Data is binary + } + if (isASCIIHex) // Check the second half of the ASCII Hex key + { + while ((i < ((uint16_t)keyLengthBytes1 * 2) && (isASCIIHex == true))) + { + if (((key1[i] >= '0') && (key1[i] <= '9')) || ((key1[i] >= 'a') && (key1[i] <= 'f')) || ((key1[i] >= 'A') && (key1[i] <= 'F'))) + i++; // Keep checking if data is all ASCII Hex + else + isASCIIHex = false; // Data is binary + } + } + + if (isASCIIHex) // Convert ASCII Hex key to binary + { + for (i = 0; i < ((uint16_t)keyLengthBytes1 * 2); i += 2) + { + if ((key1[i] >= '0') && (key1[i] <= '9')) + { + payloadCfg[20 + (i >> 1)] = (key1[i] - '0') << 4; + } + else if ((key1[i] >= 'a') && (key1[i] <= 'f')) + { + payloadCfg[20 + (i >> 1)] = (key1[i] + 10 - 'a') << 4; + } + else // if ((key1[i] >= 'A') && (key1[i] <= 'F')) + { + payloadCfg[20 + (i >> 1)] = (key1[i] + 10 - 'A') << 4; + } + + if ((key1[i + 1] >= '0') && (key1[i + 1] <= '9')) + { + payloadCfg[20 + (i >> 1)] |= key1[i + 1] - '0'; + } + else if ((key1[i + 1] >= 'a') && (key1[i + 1] <= 'f')) + { + payloadCfg[20 + (i >> 1)] |= key1[i + 1] + 10 - 'a'; + } + else // if ((key1[i + 1] >= 'A') && (key1[i + 1] <= 'F')) + { + payloadCfg[20 + (i >> 1)] |= key1[i + 1] + 10 - 'A'; + } + } + } + else // Binary key + { + memcpy(&payloadCfg[20], key1, keyLengthBytes1); + } + + // Check if all keyLengthBytes are ASCII Hex 0-9, a-f, A-F + isASCIIHex = true; + i = 0; + while ((i < (uint16_t)keyLengthBytes2) && (isASCIIHex == true)) + { + if (((key2[i] >= '0') && (key2[i] <= '9')) || ((key2[i] >= 'a') && (key2[i] <= 'f')) || ((key2[i] >= 'A') && (key2[i] <= 'F'))) + i++; // Keep checking if data is all ASCII Hex + else + isASCIIHex = false; // Data is binary + } + if (isASCIIHex) // Check the second half of the ASCII Hex key + { + while ((i < ((uint16_t)keyLengthBytes2 * 2) && (isASCIIHex == true))) + { + if (((key2[i] >= '0') && (key2[i] <= '9')) || ((key2[i] >= 'a') && (key2[i] <= 'f')) || ((key2[i] >= 'A') && (key2[i] <= 'F'))) + i++; // Keep checking if data is all ASCII Hex + else + isASCIIHex = false; // Data is binary + } + } + + if (isASCIIHex) // Convert ASCII Hex key to binary + { + for (i = 0; i < ((uint16_t)keyLengthBytes2 * 2); i += 2) + { + if ((key2[i] >= '0') && (key2[i] <= '9')) + { + payloadCfg[20 + keyLengthBytes1 + (i >> 1)] = (key2[i] - '0') << 4; + } + else if ((key2[i] >= 'a') && (key2[i] <= 'f')) + { + payloadCfg[20 + keyLengthBytes1 + (i >> 1)] = (key2[i] + 10 - 'a') << 4; + } + else // if ((key2[i] >= 'A') && (key2[i] <= 'F')) + { + payloadCfg[20 + keyLengthBytes1 + (i >> 1)] = (key2[i] + 10 - 'A') << 4; + } + + if ((key2[i + 1] >= '0') && (key2[i + 1] <= '9')) + { + payloadCfg[20 + keyLengthBytes1 + (i >> 1)] |= key2[i + 1] - '0'; + } + else if ((key2[i + 1] >= 'a') && (key2[i + 1] <= 'f')) + { + payloadCfg[20 + keyLengthBytes1 + (i >> 1)] |= key2[i + 1] + 10 - 'a'; + } + else // if ((key2[i + 1] >= 'A') && (key2[i + 1] <= 'F')) + { + payloadCfg[20 + keyLengthBytes1 + (i >> 1)] |= key2[i + 1] + 10 - 'A'; + } + } + } + else // Binary key + { + memcpy(&payloadCfg[20 + keyLengthBytes1], key2, keyLengthBytes2); + } + return (sendCommand(&packetCfg, maxWait) == SFE_UBLOX_STATUS_DATA_SENT); // We are only expecting an ACK }