diff --git a/Firmware/RTK_Everywhere/Begin.ino b/Firmware/RTK_Everywhere/Begin.ino index 579d6177e..f8c1148a5 100644 --- a/Firmware/RTK_Everywhere/Begin.ino +++ b/Firmware/RTK_Everywhere/Begin.ino @@ -189,6 +189,9 @@ void beginBoard() } else if (productVariant == RTK_TORCH) { + // Specify the GNSS radio + gnss = (GNSS *) new GNSS_UM980(); + present.psram_2mb = true; present.gnss_um980 = true; present.radio_lora = true; @@ -283,6 +286,9 @@ void beginBoard() else if (productVariant == RTK_EVK) { + // Specify the GNSS radio + gnss = (GNSS *) new GNSS_ZED(); + // Pin defs etc. for EVK v1.1 present.psram_4mb = true; present.gnss_zedf9p = true; @@ -1474,9 +1480,9 @@ void tpISR() { if (millisNow < (timTpArrivalMillis + 999)) // Only sync if the GNSS time is not stale { - if (gnssIsFullyResolved()) // Only sync if GNSS time is fully resolved + if (gnss->isFullyResolved()) // Only sync if GNSS time is fully resolved { - if (gnssGetTimeAccuracy() < 5000) // Only sync if the tAcc is better than 5000ns + if (gnss->getTimeAccuracy() < 5000) // Only sync if the tAcc is better than 5000ns { // To perform the time zone adjustment correctly, it's easiest if we convert the GNSS // time and date into Unix epoch first and then apply the timeZone offset diff --git a/Firmware/RTK_Everywhere/Bluetooth.ino b/Firmware/RTK_Everywhere/Bluetooth.ino index 59db02a16..f7688f47a 100644 --- a/Firmware/RTK_Everywhere/Bluetooth.ino +++ b/Firmware/RTK_Everywhere/Bluetooth.ino @@ -483,7 +483,7 @@ void bluetoothTest(bool runTest) { tasksStopGnssUart(); // Stop absorbing serial via task from GNSS receiver - gnssSetBaudrate(115200 * 2); + gnss->setBaudrate(115200 * 2); serialGNSS->begin(115200 * 2, SERIAL_8N1, pin_GnssUart_RX, pin_GnssUart_TX); // Start UART on platform depedent pins for SPP. The GNSS will be @@ -498,7 +498,7 @@ void bluetoothTest(bool runTest) else bluetoothStatusText = "Offline"; - gnssSetBaudrate(settings.dataPortBaud); + gnss->setBaudrate(settings.dataPortBaud); serialGNSS->begin(settings.dataPortBaud, SERIAL_8N1, pin_GnssUart_RX, pin_GnssUart_TX); // Start UART on platform depedent pins for SPP. The GNSS will be diff --git a/Firmware/RTK_Everywhere/Buttons.ino b/Firmware/RTK_Everywhere/Buttons.ino index 577ad286d..84cfd2f1a 100644 --- a/Firmware/RTK_Everywhere/Buttons.ino +++ b/Firmware/RTK_Everywhere/Buttons.ino @@ -29,7 +29,7 @@ void powerDown(bool displayInfo) // Disable SD card use endSD(false, false); - gnssStandby(); // Put the GNSS into standby - if possible + gnss->standby(); // Put the GNSS into standby - if possible // Prevent other tasks from logging, even if access to the microSD card was denied online.logging = false; diff --git a/Firmware/RTK_Everywhere/Display.ino b/Firmware/RTK_Everywhere/Display.ino index 687f5ab75..2f0bfcdb1 100644 --- a/Firmware/RTK_Everywhere/Display.ino +++ b/Firmware/RTK_Everywhere/Display.ino @@ -1267,7 +1267,7 @@ void paintHorizontalAccuracy(displayCoords textCoords) oled->setCursor(textCoords.x, textCoords.y); // x, y oled->print(":"); - float hpa = gnssGetHorizontalAccuracy(); + float hpa = gnss->getHorizontalAccuracy(); if (online.gnss == false) { @@ -1322,7 +1322,7 @@ void paintClockAccuracy(displayCoords textCoords) oled->setCursor(textCoords.x, textCoords.y); // x, y oled->print(":"); - uint32_t timeAccuracy = gnssGetTimeAccuracy(); + uint32_t timeAccuracy = gnss->getTimeAccuracy(); if (online.gnss == false) { @@ -1530,7 +1530,7 @@ displayCoords paintSIVIcon(std::vector *iconList, const ic icon = &SIVIconProperties; // Determine if there is a fix - if (gnssIsFixed() == false) + if (gnss->isFixed() == false) { // override duty - blink satellite dish icon if we don't have a fix duty = 0b01010101; @@ -1561,10 +1561,10 @@ void paintSIVText(displayCoords textCoords) if (online.gnss) { - if (gnssIsFixed() == false) + if (gnss->isFixed() == false) oled->print("0"); else - oled->print(gnssGetSatellitesInView()); + oled->print(gnss->getSatellitesInView()); paintResets(); } // End gnss online @@ -1646,8 +1646,8 @@ void paintBaseTempSurveyStarted(std::vector *iconList) oled->setCursor(xPos + 29, yPos + 2); // x, y oled->setFont(QW_FONT_8X16); - if (gnssGetSurveyInMeanAccuracy() < 10.0) // Error check - oled->print(gnssGetSurveyInMeanAccuracy(), 2); + if (gnss->getSurveyInMeanAccuracy() < 10.0) // Error check + oled->print(gnss->getSurveyInMeanAccuracy(), 2); else oled->print(">10"); @@ -1680,8 +1680,8 @@ void paintBaseTempSurveyStarted(std::vector *iconList) oled->setCursor((uint8_t)((int)xPos + SIVTextStartXPosOffset[present.display_type]) + 30, yPos + 1); // x, y oled->setFont(QW_FONT_8X16); - if (gnssGetSurveyInObservationTime() < 1000) // Error check - oled->print(gnssGetSurveyInObservationTime()); + if (gnss->getSurveyInObservationTime() < 1000) // Error check + oled->print(gnss->getSurveyInObservationTime()); else oled->print("0"); } @@ -2364,9 +2364,9 @@ void paintSystemTest() oled->print("GNSS:"); if (online.gnss == true) { - gnssUpdate(); // Regularly poll to get latest data + gnss->update(); // Regularly poll to get latest data - int satsInView = gnssGetSatellitesInView(); + int satsInView = gnss->getSatellitesInView(); if (satsInView > 5) { oled->print("OK"); diff --git a/Firmware/RTK_Everywhere/ESPNOW.ino b/Firmware/RTK_Everywhere/ESPNOW.ino index fa5b76a65..267efd7ce 100644 --- a/Firmware/RTK_Everywhere/ESPNOW.ino +++ b/Firmware/RTK_Everywhere/ESPNOW.ino @@ -112,7 +112,7 @@ void espnowOnDataReceived(const esp_now_recv_info *mac, const uint8_t *incomingD if (correctionLastSeen(CORR_ESPNOW)) { // Pass RTCM bytes (presumably) from ESP NOW out ESP32-UART to GNSS - gnssPushRawData((uint8_t *)incomingData, len); + gnss->pushRawData((uint8_t *)incomingData, len); if ((settings.debugEspNow == true || settings.debugCorrections == true) && !inMainMenu) systemPrintf("ESPNOW received %d RTCM bytes, pushed to GNSS, RSSI: %d\r\n", len, espnowRSSI); diff --git a/Firmware/RTK_Everywhere/Form.ino b/Firmware/RTK_Everywhere/Form.ino index c91b4aabd..55ed46608 100644 --- a/Firmware/RTK_Everywhere/Form.ino +++ b/Firmware/RTK_Everywhere/Form.ino @@ -873,15 +873,15 @@ void createDynamicDataString(char *settingsCSV) settingsCSV[0] = '\0'; // Erase current settings string // Current coordinates come from HPPOSLLH call back - stringRecord(settingsCSV, "geodeticLat", gnssGetLatitude(), haeNumberOfDecimals); - stringRecord(settingsCSV, "geodeticLon", gnssGetLongitude(), haeNumberOfDecimals); - stringRecord(settingsCSV, "geodeticAlt", gnssGetAltitude(), 3); + stringRecord(settingsCSV, "geodeticLat", gnss->getLatitude(), haeNumberOfDecimals); + stringRecord(settingsCSV, "geodeticLon", gnss->getLongitude(), haeNumberOfDecimals); + stringRecord(settingsCSV, "geodeticAlt", gnss->getAltitude(), 3); double ecefX = 0; double ecefY = 0; double ecefZ = 0; - geodeticToEcef(gnssGetLatitude(), gnssGetLongitude(), gnssGetAltitude(), &ecefX, &ecefY, &ecefZ); + geodeticToEcef(gnss->getLatitude(), gnss->getLongitude(), gnss->getAltitude(), &ecefX, &ecefY, &ecefZ); stringRecord(settingsCSV, "ecefX", ecefX, 3); stringRecord(settingsCSV, "ecefY", ecefY, 3); @@ -1108,7 +1108,8 @@ void createMessageListBase(String &returnText) if (present.gnss_zedf9p) { - int firstRTCMRecord = zedGetMessageNumberByName("RTCM_1005"); + GNSS_ZED * zed = (GNSS_ZED *)gnss; + int firstRTCMRecord = zed->getMessageNumberByName("RTCM_1005"); for (int messageNumber = 0; messageNumber < MAX_UBX_MSG_RTCM; messageNumber++) { diff --git a/Firmware/RTK_Everywhere/GNSS.h b/Firmware/RTK_Everywhere/GNSS.h new file mode 100644 index 000000000..c1a54c2ca --- /dev/null +++ b/Firmware/RTK_Everywhere/GNSS.h @@ -0,0 +1,367 @@ +/*------------------------------------------------------------------------------ +GNSS.h + + Declarations and definitions for the GNSS layer +------------------------------------------------------------------------------*/ + +#ifndef __GNSS_H__ +#define __GNSS_H__ + +class GNSS +{ + protected: + float _altitude; // Altitude in meters + float _horizontalAccuracy; // Horizontal position accuracy in meters + double _latitude; // Latitude in degrees + double _longitude; // Longitude in degrees + + uint8_t _day; // Day number + uint8_t _month; // Month number + uint16_t _year; + uint8_t _hour; // Hours for 24 hour clock + uint8_t _minute; + uint8_t _second; + uint16_t _millisecond; // Limited to first two digits + uint32_t _nanosecond; + + uint8_t _satellitesInView; + uint8_t _fixType; + uint8_t _carrierSolution; + + bool _validDate; // True when date is valid + bool _validTime; // True when time is valid + bool _confirmedDate; + bool _confirmedTime; + bool _fullyResolved; + uint32_t _tAcc; + + unsigned long _pvtArrivalMillis; + bool _pvtUpdated; + + // Setup the general configuration of the GNSS + // Not Rover or Base specific (ie, baud rates) + // Outputs: + // Returns true if successfully configured and false upon failure + virtual bool configureRadio(); + + // Set the minimum satellite signal level for navigation. + virtual bool setMinCnoRadio (uint8_t cnoValue); + + public: + + // Temporarily make these public + unsigned long _autoBaseStartTimer; // Tracks how long the base auto / averaging mode has been running + uint8_t _leapSeconds; + + // Constructor + GNSS() : _leapSeconds(18), _pvtArrivalMillis(0), _pvtUpdated(0) + { + } + + // If we have decryption keys, configure module + // Note: don't check online.lband_neo here. We could be using ip corrections + virtual void applyPointPerfectKeys(); + + // Set RTCM for base mode to defaults (1005/1074/1084/1094/1124 1Hz & 1230 0.1Hz) + virtual void baseRtcmDefault(); + + // Reset to Low Bandwidth Link (1074/1084/1094/1124 0.5Hz & 1005/1230 0.1Hz) + virtual void baseRtcmLowDataRate(); + + // Connect to GNSS and identify particulars + virtual void begin(); + + // Setup TM2 time stamp input as need + // Outputs: + // Returns true when an external event occurs and false if no event + virtual bool beginExternalEvent(); + + // Setup the timepulse output on the PPS pin for external triggering + // Outputs + // Returns true if the pin was successfully setup and false upon + // failure + virtual bool beginPPS(); + + virtual bool checkNMEARates(); + + virtual bool checkPPPRates(); + + // Setup the general configuration of the GNSS + // Not Rover or Base specific (ie, baud rates) + // Outputs: + // Returns true if successfully configured and false upon failure + bool configure(); + + // Configure the Base + // Outputs: + // Returns true if successfully configured and false upon failure + virtual bool configureBase(); + + // Configure specific aspects of the receiver for NTP mode + virtual bool configureNtpMode(); + + // Configure the Rover + // Outputs: + // Returns true if successfully configured and false upon failure + virtual bool configureRover(); + + virtual void debuggingDisable(); + + virtual void debuggingEnable(); + + virtual void enableGgaForNtrip(); + + // Enable RTCM 1230. This is the GLONASS bias sentence and is transmitted + // even if there is no GPS fix. We use it to test serial output. + // Outputs: + // Returns true if successfully started and false upon failure + virtual bool enableRTCMTest(); + + // Restore the GNSS to the factory settings + virtual void factoryReset(); + + virtual uint16_t fileBufferAvailable(); + + virtual uint16_t fileBufferExtractData(uint8_t *fileBuffer, int fileBytesToRead); + + // Start the base using fixed coordinates + // Outputs: + // Returns true if successfully started and false upon failure + virtual bool fixedBaseStart(); + + // Return the number of active/enabled messages + virtual uint8_t getActiveMessageCount(); + + // Get the altitude + // Outputs: + // Returns the altitude in meters or zero if the GNSS is offline + virtual double getAltitude(); + + // Returns the carrier solution or zero if not online + virtual uint8_t getCarrierSolution(); + + virtual uint32_t getDataBaudRate(); + + // Returns the day number or zero if not online + virtual uint8_t getDay(); + + // Return the number of milliseconds since GNSS data was last updated + virtual uint16_t getFixAgeMilliseconds(); + + // Returns the fix type or zero if not online + virtual uint8_t getFixType(); + + // Returns the hours of 24 hour clock or zero if not online + virtual uint8_t getHour(); + + // Get the horizontal position accuracy + // Outputs: + // Returns the horizontal position accuracy or zero if offline + virtual float getHorizontalAccuracy(); + + virtual const char * getId(); + + // Get the latitude value + // Outputs: + // Returns the latitude value or zero if not online + virtual double getLatitude(); + + // Query GNSS for current leap seconds + virtual uint8_t getLeapSeconds(); + + // Get the longitude value + // Outputs: + // Returns the longitude value or zero if not online + virtual double getLongitude(); + + // Returns two digits of milliseconds or zero if not online + virtual uint8_t getMillisecond(); + + // Get the minimum satellite signal level for navigation. + uint8_t getMinCno(); + + // Returns minutes or zero if not online + virtual uint8_t getMinute(); + + // Returns month number or zero if not online + virtual uint8_t getMonth(); + + // Returns nanoseconds or zero if not online + virtual uint32_t getNanosecond(); + + virtual uint32_t getRadioBaudRate(); + + // Returns the seconds between solutions + virtual double getRateS(); + + virtual const char * getRtcmDefaultString(); + + virtual const char * getRtcmLowDataRateString(); + + // Returns the number of satellites in view or zero if offline + virtual uint8_t getSatellitesInView(); + + // Returns seconds or zero if not online + virtual uint8_t getSecond(); + + // Get the survey-in mean accuracy + // Outputs: + // Returns the mean accuracy or zero (0) + virtual float getSurveyInMeanAccuracy(); + + // Return the number of seconds the survey-in process has been running + virtual int getSurveyInObservationTime(); + + float getSurveyInStartingAccuracy(); + + // Returns timing accuracy or zero if not online + virtual uint32_t getTimeAccuracy(); + + // Returns full year, ie 2023, not 23. + virtual uint16_t getYear(); + + virtual bool isBlocking(); + + // Date is confirmed once we have GNSS fix + virtual bool isConfirmedDate(); + + // Date is confirmed once we have GNSS fix + virtual bool isConfirmedTime(); + + // Return true if GNSS receiver has a higher quality DGPS fix than 3D + virtual bool isDgpsFixed(); + + // Some functions (L-Band area frequency determination) merely need + // to know if we have a valid fix, not what type of fix + // This function checks to see if the given platform has reached + // sufficient fix type to be considered valid + virtual bool isFixed(); + + // Used in tpISR() for time pulse synchronization + virtual bool isFullyResolved(); + + virtual bool isPppConverged(); + + virtual bool isPppConverging(); + + // Some functions (L-Band area frequency determination) merely need + // to know if we have an RTK Fix. This function checks to see if the + // given platform has reached sufficient fix type to be considered valid + virtual bool isRTKFix(); + + // Some functions (L-Band area frequency determination) merely need + // to know if we have an RTK Float. This function checks to see if + // the given platform has reached sufficient fix type to be considered + // valid + virtual bool isRTKFloat(); + + // Determine if the survey-in operation is complete + // Outputs: + // Returns true if the survey-in operation is complete and false + // if the operation is still running + virtual bool isSurveyInComplete(); + + // Date will be valid if the RTC is reporting (regardless of GNSS fix) + virtual bool isValidDate(); + + // Time will be valid if the RTC is reporting (regardless of GNSS fix) + virtual bool isValidTime(); + + // Controls the constellations that are used to generate a fix and logged + virtual void menuConstellations(); + + virtual void menuMessageBaseRtcm(); + + // Control the messages that get broadcast over Bluetooth and logged (if enabled) + virtual void menuMessages(); + + // Print the module type and firmware version + virtual void printModuleInfo(); + + // Send correction data to the GNSS + // Inputs: + // dataToSend: Address of a buffer containing the data + // dataLength: The number of valid data bytes in the buffer + // Outputs: + // Returns the number of correction data bytes written + virtual int pushRawData(uint8_t *dataToSend, int dataLength); + + virtual uint16_t rtcmBufferAvailable(); + + // If LBand is being used, ignore any RTCM that may come in from the GNSS + virtual void rtcmOnGnssDisable(); + + // If L-Band is available, but encrypted, allow RTCM through other sources (radio, ESP-Now) to GNSS receiver + virtual void rtcmOnGnssEnable(); + + virtual uint16_t rtcmRead(uint8_t *rtcmBuffer, int rtcmBytesToRead); + + // Save the current configuration + // Outputs: + // Returns true when the configuration was saved and false upon failure + virtual bool saveConfiguration(); + + // Set the baud rate on the GNSS port that interfaces between the ESP32 and the GNSS + // This just sets the GNSS side + // Used during Bluetooth testing + // Inputs: + // baudRate: The desired baudrate + virtual bool setBaudrate(uint32_t baudRate); + + // Enable all the valid constellations and bands for this platform + virtual bool setConstellations(); + + virtual bool setDataBaudRate(uint32_t baud); + + // Set the elevation in degrees + // Inputs: + // elevationDegrees: The elevation value in degrees + virtual bool setElevation(uint8_t elevationDegrees); + + // Enable all the valid messages for this platform + virtual bool setMessages(int maxRetries); + + // Enable all the valid messages for this platform over the USB port + virtual bool setMessagesUsb(int maxRetries); + + // Set the minimum satellite signal level for navigation. + bool setMinCno(uint8_t cnoValue); + + // Set the dynamic model to use for RTK + // Inputs: + // modelNumber: Number of the model to use, provided by radio library + virtual bool setModel(uint8_t modelNumber); + + virtual bool setRadioBaudRate(uint32_t baud); + + // Specify the interval between solutions + // Inputs: + // secondsBetweenSolutions: Number of seconds between solutions + // Outputs: + // Returns true if the rate was successfully set and false upon + // failure + virtual bool setRate(double secondsBetweenSolutions); + + virtual bool setTalkerGNGGA(); + + // Hotstart GNSS to try to get RTK lock + virtual bool softwareReset(); + + virtual bool standby(); + + // Reset the survey-in operation + // Outputs: + // Returns true if the survey-in operation was reset successfully + // and false upon failure + virtual bool surveyInReset(); + + // Start the survey-in operation + // Outputs: + // Return true if successful and false upon failure + virtual bool surveyInStart(); + + // Poll routine to update the GNSS state + virtual void update(); +}; + +#endif // __GNSS_H__ diff --git a/Firmware/RTK_Everywhere/GNSS.ino b/Firmware/RTK_Everywhere/GNSS.ino index 43687bd1e..15b21e2b6 100644 --- a/Firmware/RTK_Everywhere/GNSS.ino +++ b/Firmware/RTK_Everywhere/GNSS.ino @@ -1,17 +1,15 @@ -// Connect to GNSS and identify particulars -void gnssBegin() -{ - if (present.gnss_zedf9p) - zedBegin(); - else if (present.gnss_um980) - um980Begin(); - else if (present.gnss_mosaicX5) - mosaicX5Begin(); -} +/*------------------------------------------------------------------------------ +GNSS.ino + GNSS layer implementation +------------------------------------------------------------------------------*/ + +//---------------------------------------- // Setup the general configuration of the GNSS // Not Rover or Base specific (ie, baud rates) -bool gnssConfigure() +// Returns true if successfully configured and false otherwise +//---------------------------------------- +bool GNSS::configure() { if (online.gnss == false) return (false); @@ -19,1775 +17,32 @@ bool gnssConfigure() // Check various setting arrays (message rates, etc) to see if they need to be reset to defaults checkGNSSArrayDefaults(); - if (present.gnss_zedf9p) - { - // Configuration can take >1s so configure during splash - if (zedConfigure() == false) - return (false); - } - else if (present.gnss_um980) - { - if (um980Configure() == false) - return (false); - } - else if (present.gnss_mosaicX5) - { - if (mosaicX5Configure() == false) - return (false); - } - - return (true); -} - -bool gnssConfigureRover() -{ - if (online.gnss == true) - { - if (present.gnss_zedf9p) - { - return (zedConfigureRover()); - } - else if (present.gnss_um980) - { - return (um980ConfigureRover()); - } - else if (present.gnss_mosaicX5) - { - return (mosaicX5ConfigureRover()); - } - } - return (false); -} - -bool gnssConfigureBase() -{ - if (online.gnss == true) - { - if (present.gnss_zedf9p) - { - return (zedConfigureBase()); - } - else if (present.gnss_um980) - { - return (um980ConfigureBase()); - } - else if (present.gnss_mosaicX5) - { - return (mosaicX5ConfigureBase()); - } - } - return (false); -} - -void gnssUpdate() -{ - if (online.gnss == true) - { - if (present.gnss_zedf9p) - { - theGNSS->checkUblox(); // Regularly poll to get latest data and any RTCM - theGNSS->checkCallbacks(); // Process any callbacks: ie, eventTriggerReceived - } - else if (present.gnss_um980) - { - // We don't check serial data here; the gnssReadTask takes care of serial consumption - } - else if (present.gnss_mosaicX5) - { - // We don't check serial data here; the gnssReadTask takes care of serial consumption - mosaicX5Housekeeping(); // Housekeeping - update sdFreeSpace, logIncreasing etc. - } - } -} - -bool gnssSurveyInStart() -{ - if (online.gnss == true) - { - if (present.gnss_zedf9p) - { - return (zedSurveyInStart()); - } - else if (present.gnss_um980) - { - return (um980BaseAverageStart()); - } - else if (present.gnss_mosaicX5) - { - return (mosaicX5AutoBaseStart()); // setPVTMode,Static, ,auto - } - } - return (false); -} - -bool gnssIsSurveyComplete() -{ - if (online.gnss == true) - { - if (present.gnss_zedf9p) - { - return (theGNSS->getSurveyInValid(50)); - } - else if (present.gnss_um980) - { - // Return true once enough time, since the start of the base mode, has elapsed - int elapsedSeconds = (millis() - autoBaseStartTimer) / 1000; - - if (elapsedSeconds > settings.observationSeconds) - return (true); - return (false); - } - else if (present.gnss_mosaicX5) - { - // Bit 6: Set if the user has entered the command setPVTMode, Static, , auto - // and the receiver is still in the process of determining its fixed position. - return (mosaicX5AutoBaseComplete()); - } - } - return (false); -} - -bool gnssSurveyInReset() -{ - if (online.gnss == true) - { - if (present.gnss_zedf9p) - { - return (zedSurveyInReset()); - } - else if (present.gnss_um980) - { - // Put UM980 into rover mode to cancel base averaging mode - return (um980SetModeRoverSurvey()); - } - else if (present.gnss_mosaicX5) - { - // Put mosaicX5 into rover mode to cancel auto base mode - return (mosaicX5SurveyReset()); - } - } - return (false); -} - -bool gnssFixedBaseStart() -{ - if (online.gnss == true) - { - if (present.gnss_zedf9p) - { - return (zedFixedBaseStart()); - } - else if (present.gnss_um980) - { - return (um980FixedBaseStart()); - } - else if (present.gnss_mosaicX5) - { - return (mosaicX5FixedBaseStart()); - } - } - return (false); -} - -void gnssEnableRTCMTest() -{ - // Enable RTCM 1230. This is the GLONASS bias sentence and is transmitted - // even if there is no GPS fix. We use it to test serial output. - if (online.gnss == true) - { - if (present.gnss_zedf9p) - { - theGNSS->newCfgValset(); // Create a new Configuration Item VALSET message - theGNSS->addCfgValset(UBLOX_CFG_MSGOUT_RTCM_3X_TYPE1230_UART2, 1); // Enable message 1230 every second - theGNSS->sendCfgValset(); // Send the VALSET - } - else if (present.gnss_um980) - { - // There is no data port on devices with the UM980 - } - else if (present.gnss_mosaicX5) - { - // Enable RTCM1230 on COM2 (Radio connector) - mosaicX5EnableRTCMTest(); - } - } -} - -// If LBand is being used, ignore any RTCM that may come in from the GNSS -void gnssDisableRtcmOnGnss() -{ - if (online.gnss == true) - { - if (present.gnss_zedf9p) - { - theGNSS->setUART2Input(COM_TYPE_UBX); // Set ZED's UART2 to input UBX (no RTCM) - } - else if (present.gnss_um980) - { - // UM980 does not have a separate interface for RTCM - } - else if (present.gnss_mosaicX5) - { - // TODO: is this needed? - } - } -} - -// If L-Band is available, but encrypted, allow RTCM through other sources (radio, ESP-Now) to GNSS receiver -// UNUSED. TODO: delete this? -void gnssEnableRtcmOnGnss() -{ - if (online.gnss == true) - { - if (present.gnss_zedf9p) - { - theGNSS->setUART2Input(COM_TYPE_RTCM3); // Set the ZED's UART2 to input RTCM - } - else if (present.gnss_um980) - { - // UM980 does not have separate interface for RTCM - } - else if (present.gnss_mosaicX5) - { - // TODO: is this needed? - } - } -} - -// Return the number of seconds the survey-in process has been running -int gnssGetSurveyInObservationTime() -{ - static uint16_t svinObservationTime = 0; - static unsigned long lastCheck = 0; - - if (online.gnss == true) - { - if (present.gnss_zedf9p) - { - // Use a local static so we don't have to request these values multiple times (ZED takes many ms to respond - // to this command) - if (millis() - lastCheck > 1000) - { - lastCheck = millis(); - svinObservationTime = theGNSS->getSurveyInObservationTime(50); - } - return (svinObservationTime); - } - else if (present.gnss_um980 || present.gnss_mosaicX5) - { - int elapsedSeconds = (millis() - autoBaseStartTimer) / 1000; - return (elapsedSeconds); - } - } - return (0); -} - -// TODO make sure we're not slowing down a ZED base -float gnssGetSurveyInMeanAccuracy() -{ - static float svinMeanAccuracy = 0; - static unsigned long lastCheck = 0; - - if (online.gnss == true) - { - if (present.gnss_zedf9p) - { - // Use a local static so we don't have to request these values multiple times (ZED takes many ms to respond - // to this command) - if (millis() - lastCheck > 1000) - { - lastCheck = millis(); - svinMeanAccuracy = theGNSS->getSurveyInMeanAccuracy(50); - } - return (svinMeanAccuracy); - } - else if (present.gnss_um980) - { - // Not supported on the UM980 - // Return the current HPA instead - return (um980GetHorizontalAccuracy()); - } - else if (present.gnss_mosaicX5) - { - // Not supported on the mosaicX5 - // Return the current HPA instead - return (mosaicX5GetHorizontalAccuracy()); - } - } - return (0); -} - -// Setup TM2 time stamp input as need -bool gnssBeginExternalEvent() -{ - if (online.gnss == true) - { - if (present.gnss_zedf9p) - { - return (zedBeginExternalEvent()); - } - else if (present.gnss_um980) - { - // UM980 Event signal not exposed - } - else if (present.gnss_mosaicX5) - { - return (mosaicX5BeginExternalEvent()); - } - } - return (false); -} - -// Setup the timepulse output on the PPS pin for external triggering -bool gnssBeginPPS() -{ - if (online.gnss == true) - { - if (present.gnss_zedf9p) - { - return (zedBeginPPS()); - } - else if (present.gnss_um980) - { - // UM980 PPS signal not exposed - } - else if (present.gnss_mosaicX5) - { - return (mosaicX5BeginPPS()); - // spps - } - } - return (false); -} - -// Set the baud rate on the GNSS port that interfaces between the ESP32 and the GNSS -// This just sets the GNSS side -// Used during Bluetooth testing -void gnssSetBaudrate(uint32_t baudRate) -{ - if (online.gnss == true) - { - if (present.gnss_zedf9p) - { - theGNSS->setVal32(UBLOX_CFG_UART1_BAUDRATE, - (115200 * 2)); // Defaults to 230400 to maximize message output support - } - else if (present.gnss_um980) - { - // Set the baud rate on COM3 of the UM980 - um980SetBaudRateCOM3(baudRate); - } - else if (present.gnss_mosaicX5) - { - // Set the baud rate on COM1 of the X5 - mosaicX5SetBaudRateCOM(1, baudRate); - } - } -} - -int gnssPushRawData(uint8_t *dataToSend, int dataLength) -{ - if (online.gnss == true) - { - if (present.gnss_zedf9p) - { - return (theGNSS->pushRawData((uint8_t *)dataToSend, dataLength)); - } - else if (present.gnss_um980) - { - // Send data directly from ESP GNSS UART to UM980 UART3 - return (um980PushRawData((uint8_t *)dataToSend, dataLength)); - } - else if (present.gnss_mosaicX5) - { - // Send data directly from ESP GNSS UART to mosaic-X5 COM1 - return (mosaicX5PushRawData((uint8_t *)dataToSend, dataLength)); - } - } - return (0); -} - -bool gnssSetRate(double secondsBetweenSolutions) -{ - if (online.gnss == true) - { - if (present.gnss_zedf9p) - { - return (zedSetRate(secondsBetweenSolutions)); - } - else if (present.gnss_um980) - { - return (um980SetRate(secondsBetweenSolutions)); - } - else if (present.gnss_mosaicX5) - { - // The X5 doesn't have a navigation rate. Instead, we set the - // message rate multiplier for sr3i, sno etc. - return (mosaicX5SetRate(secondsBetweenSolutions)); - } - } - return (false); -} - -// Returns the seconds between solutions -double gnssGetRateS(void) -{ - if (online.gnss == true) - { - if (present.gnss_zedf9p) - { - return (zedGetRateS()); - } - else if (present.gnss_um980) - { - return (um980GetRateS()); - } - else if (present.gnss_mosaicX5) - { - return (mosaicX5GetRateS()); - } - } - return (0.0); -} - -bool gnssSaveConfiguration() -{ - if (online.gnss == true) - { - if (present.gnss_zedf9p) - { - zedSaveConfiguration(); - return (true); - } - else if (present.gnss_um980) - { - return (um980SaveConfiguration()); - } - else if (present.gnss_mosaicX5) - { - return (mosaicX5SaveConfiguration()); - } - } - return (false); -} - -void gnssFactoryReset() -{ - if (online.gnss == true) - { - if (present.gnss_zedf9p) - { - zedFactoryReset(); - } - else if (present.gnss_um980) - { - um980FactoryReset(); - } - else if (present.gnss_mosaicX5) - { - mosaicX5FactoryReset(); - } - } -} - -void gnssSetModel(uint8_t modelNumber) -{ - if (online.gnss == true) - { - if (present.gnss_zedf9p) - { - theGNSS->setVal8(UBLOX_CFG_NAVSPG_DYNMODEL, (dynModel)modelNumber); // Set dynamic model - } - else if (present.gnss_um980) - { - um980SetModel(modelNumber); - } - else if (present.gnss_mosaicX5) - { - mosaicX5SetModel(modelNumber); - } - } -} - -void gnssSetElevation(uint8_t elevationDegrees) -{ - if (online.gnss == true) - { - if (present.gnss_zedf9p) - { - zedSetElevation(elevationDegrees); - } - else if (present.gnss_um980) - { - um980SetMinElevation(elevationDegrees); - } - else if (present.gnss_mosaicX5) - { - mosaicX5SetMinElevation(elevationDegrees); // sem - } - } + // Configure the radio + return configureRadio(); } -void gnssSetMinCno(uint8_t cnoValue) -{ - if (online.gnss == true) - { - if (present.gnss_zedf9p) - { - zedSetMinCno(cnoValue); - settings.minCNO = cnoValue; // Update the setting - } - else if (present.gnss_um980) - { - um980SetMinCNO(cnoValue); - settings.minCNO = cnoValue; // Update the setting - } - else if (present.gnss_mosaicX5) - { - mosaicX5SetMinCNO(cnoValue); // scm - settings.minCNO = cnoValue; // Update the setting - } - } -} - -uint8_t gnssGetMinCno() +//---------------------------------------- +// Get the minimum satellite signal level for navigation. +//---------------------------------------- +uint8_t GNSS::getMinCno() { return (settings.minCNO); } -double gnssGetLatitude() -{ - if (online.gnss == true) - { - if (present.gnss_zedf9p) - { - return (zedGetLatitude()); - } - else if (present.gnss_um980) - { - return (um980GetLatitude()); - } - else if (present.gnss_mosaicX5) - { - return (mosaicX5GetLatitude()); - } - } - return (0); -} - -double gnssGetLongitude() -{ - if (online.gnss == true) - { - if (present.gnss_zedf9p) - { - return (zedGetLongitude()); - } - else if (present.gnss_um980) - { - return (um980GetLongitude()); - } - else if (present.gnss_mosaicX5) - { - return (mosaicX5GetLongitude()); - } - } - return (0); -} - -double gnssGetAltitude() -{ - if (online.gnss == true) - { - if (present.gnss_zedf9p) - { - return (zedGetAltitude()); - } - else if (present.gnss_um980) - { - return (um980GetAltitude()); - } - else if (present.gnss_mosaicX5) - { - return (mosaicX5GetAltitude()); - } - } - return (0); -} - -// Date and Time will be valid if ZED's RTC is reporting (regardless of GNSS fix) -bool gnssIsValidDate() -{ - if (online.gnss == true) - { - if (present.gnss_zedf9p) - { - return (zedIsValidDate()); - } - else if (present.gnss_um980) - { - return (um980IsValidDate()); - } - else if (present.gnss_mosaicX5) - { - return (mosaicX5IsValidDate()); - } - } - return (false); -} -bool gnssIsValidTime() -{ - if (online.gnss == true) - { - if (present.gnss_zedf9p) - { - return (zedIsValidTime()); - } - else if (present.gnss_um980) - { - return (um980IsValidTime()); - } - else if (present.gnss_mosaicX5) - { - return (mosaicX5IsValidTime()); - } - } - return (false); -} - -// Date and Time are confirmed once we have GNSS fix -bool gnssIsConfirmedDate() -{ - if (online.gnss == true) - { - if (present.gnss_zedf9p) - { - return (zedIsConfirmedDate()); - } - else if (present.gnss_um980) - { - // UM980 doesn't have this feature. Check for valid date. - return (um980IsValidDate()); - } - else if (present.gnss_mosaicX5) - { - // UM980 doesn't have this feature. Check for valid date. - return (mosaicX5IsValidDate()); - } - } - return (false); -} -bool gnssIsConfirmedTime() -{ - if (online.gnss == true) - { - if (present.gnss_zedf9p) - { - return (zedIsConfirmedTime()); - } - else if (present.gnss_um980) - { - // UM980 doesn't have this feature. Check for valid time. - return (um980IsValidTime()); - } - else if (present.gnss_mosaicX5) - { - // UM980 doesn't have this feature. Check for valid time. - return (mosaicX5IsValidTime()); - } - } - return (false); -} - -// Used in tpISR() for time pulse synchronization -bool gnssIsFullyResolved() -{ - if (online.gnss == true) - { - if (present.gnss_zedf9p) - { - return (zedIsFullyResolved()); - } - else if (present.gnss_um980) - { - return (um980IsFullyResolved()); - } - else if (present.gnss_mosaicX5) - { - return (mosaicX5IsFullyResolved()); - } - } - return (false); -} - -// Used in tpISR() for time pulse synchronization -uint32_t gnssGetTimeAccuracy() -{ - if (online.gnss == true) - { - if (present.gnss_zedf9p) - { - return (zedGetTimeAccuracy()); // Returns nanoseconds - } - else if (present.gnss_um980) - { - return (um980GetTimeDeviation()); // Returns nanoseconds - } - else if (present.gnss_mosaicX5) - { - return (mosaicX5GetTimeDeviation()); - } - } - return (0); -} - -uint8_t gnssGetSatellitesInView() -{ - if (online.gnss == true) - { - if (present.gnss_zedf9p) - { - return (zedGetSatellitesInView()); - } - else if (present.gnss_um980) - { - return (um980GetSatellitesInView()); - } - else if (present.gnss_mosaicX5) - { - return (mosaicX5GetSatellitesInView()); - } - } - return (0); -} - -// ZED: 0 = no fix, 1 = dead reckoning only, 2 = 2D-fix, 3 = 3D-fix, 4 = GNSS + dead reckoning combined, 5 = time only -// fix -// UM980: 0 = None, 1 = FixedPos, 8 = DopplerVelocity, 16 = Single, ... -uint8_t gnssGetFixType() -{ - if (online.gnss == true) - { - if (present.gnss_zedf9p) - { - return (zedGetFixType()); // 0 = no fix, 1 = dead reckoning only, 2 = 2D-fix, 3 = 3D-fix, 4 = GNSS + dead - // reckoning combined, 5 = time only fix - } - else if (present.gnss_um980) - { - return (um980GetPositionType()); // 0 = None, 1 = FixedPos, 8 = DopplerVelocity, 16 = Single, ... - } - else if (present.gnss_mosaicX5) - { - // Bits 0-3: type of PVT solution: - // 0: No GNSS PVT available - // 1: Stand-Alone PVT - // 2: Differential PVT - // 3: Fixed location - // 4: RTK with fixed ambiguities - // 5: RTK with float ambiguities - // 6: SBAS aided PVT - // 7: moving-base RTK with fixed ambiguities - // 8: moving-base RTK with float ambiguities - // 9: Reserved - // 10: Precise Point Positioning (PPP) - // 12: Reserved - return (mosaicX5GetPositionType()); - } - } - return (0); -} - -// Some functions (L-Band area frequency determination) merely need to know if we have a valid fix, not what type of fix -// This function checks to see if the given platform has reached sufficient fix type to be considered valid -bool gnssIsFixed() -{ - if (online.gnss == true) - { - if (present.gnss_zedf9p) - { - if (zedGetFixType() >= 3) // 0 = no fix, 1 = dead reckoning only, 2 = 2D-fix, 3 = 3D-fix, 4 = GNSS + dead - // reckoning combined, 5 = time only fix - return (true); - } - else if (present.gnss_um980) - { - if (um980GetPositionType() >= 16) // 16 = 3D Fix (Single) - return (true); - } - else if (present.gnss_mosaicX5) - { - // Bits 0-3: type of PVT solution: - // 0: No GNSS PVT available - // 1: Stand-Alone PVT - // 2: Differential PVT - // 3: Fixed location - // 4: RTK with fixed ambiguities - // 5: RTK with float ambiguities - // 6: SBAS aided PVT - // 7: moving-base RTK with fixed ambiguities - // 8: moving-base RTK with float ambiguities - // 9: Reserved - // 10: Precise Point Positioning (PPP) - // 12: Reserved - if (mosaicX5GetPositionType() > 0) - return (true); - } - } - return (false); -} - -// Return true if GNSS receiver has a higher quality DGPS fix than 3D -bool gnssIsDgpsFixed() -{ - if (online.gnss == true) - { - if (present.gnss_zedf9p) - { - // Not supported - return (false); - } - else if (present.gnss_um980) - { - if (um980GetPositionType() == 17) // 17 = Pseudorange differential solution - return (true); - } - else if (present.gnss_mosaicX5) - { - // 2: Differential PVT - // 6: SBAS aided PVT - if ((mosaicX5GetPositionType() == 2) || (mosaicX5GetPositionType() == 6)) - return (true); - } - } - return (false); -} - -// ZED: 0 = No RTK, 1 = RTK Float, 2 = RTK Fix -// UM980: 0 = Solution computed, 1 = Insufficient observation, 3 = No convergence, 4 = Covariance trace -// mosaic-X5: 0 = No RTK, 1 = RTK Float (Mode = 5), 2 = RTK Fixed (Mode = 4) -uint8_t gnssGetCarrierSolution() -{ - if (online.gnss == true) - { - if (present.gnss_zedf9p) - { - return (zedGetCarrierSolution()); - } - else if (present.gnss_um980) - { - return (um980GetSolutionStatus()); - } - else if (present.gnss_mosaicX5) - { - return (mosaicX5GetSolutionStatus()); - } - } - return (0); -} - -// Some functions (L-Band area frequency determination) merely need to know if we have an RTK Fix. -// This function checks to see if the given platform has reached sufficient fix type to be considered valid -bool gnssIsRTKFix() -{ - if (online.gnss == true) - { - if (present.gnss_zedf9p) - { - if (zedGetCarrierSolution() == 2) // 0 = No RTK, 1 = RTK Float, 2 = RTK Fix - return (true); - } - else if (present.gnss_um980) - { - if (um980GetPositionType() == 50) // 50 = RTK Fixed (Narrow-lane fixed solution) - return (true); - } - else if (present.gnss_mosaicX5) - { - // 4: RTK with fixed ambiguities - if (mosaicX5GetPositionType() == 4) - return (true); - } - } - return (false); -} - -// Some functions (L-Band area frequency determination) merely need to know if we have an RTK Float. -// This function checks to see if the given platform has reached sufficient fix type to be considered valid -bool gnssIsRTKFloat() -{ - if (online.gnss == true) - { - if (present.gnss_zedf9p) - { - if (zedGetCarrierSolution() == 1) // 0 = No RTK, 1 = RTK Float, 2 = RTK Fix - return (true); - } - else if (present.gnss_um980) - { - if (um980GetPositionType() == 49 || - um980GetPositionType() == 34) // 49 = Wide-lane fixed solution, 34 = Narrow-land float solution - return (true); - } - else if (present.gnss_mosaicX5) - { - // 5: RTK with float ambiguities - if (mosaicX5GetPositionType() == 5) - return (true); - } - } - return (false); -} - -bool gnssIsPppConverging() -{ - if (online.gnss == true) - { - if (present.gnss_zedf9p) - { - return (false); - } - else if (present.gnss_um980) - { - if (um980GetPositionType() == 68) // 68 = PPP solution converging - return (true); - } - else if (present.gnss_mosaicX5) - { - // 10: Precise Point Positioning (PPP) ? Is this what we want? TODO - if (mosaicX5GetPositionType() == 10) - return (true); - } - } - return (false); -} - -bool gnssIsPppConverged() -{ - if (online.gnss == true) - { - if (present.gnss_zedf9p) - { - return (false); - } - else if (present.gnss_um980) - { - if (um980GetPositionType() == 69) // 69 = Precision Point Positioning - return (true); - } - else if (present.gnss_mosaicX5) - { - // 10: Precise Point Positioning (PPP) ? Is this what we want? TODO - if (mosaicX5GetPositionType() == 10) - return (true); - } - } - return (false); -} - -float gnssGetHorizontalAccuracy() -{ - if (online.gnss == true) - { - if (present.gnss_zedf9p) - { - return (zedGetHorizontalAccuracy()); - } - else if (present.gnss_um980) - { - return (um980GetHorizontalAccuracy()); - } - else if (present.gnss_mosaicX5) - { - return (mosaicX5GetHorizontalAccuracy()); - } - } - return (0); -} - -// Return full year, ie 2023, not 23. -uint16_t gnssGetYear() -{ - if (online.gnss == true) - { - if (present.gnss_zedf9p) - { - return (zedGetYear()); - } - else if (present.gnss_um980) - { - return (um980GetYear()); - } - else if (present.gnss_mosaicX5) - { - return (mosaicX5GetYear()); - } - } - return (0); -} -uint8_t gnssGetMonth() -{ - if (online.gnss == true) - { - if (present.gnss_zedf9p) - { - return (zedGetMonth()); - } - else if (present.gnss_um980) - { - return (um980GetMonth()); - } - else if (present.gnss_mosaicX5) - { - return (mosaicX5GetMonth()); - } - } - return (0); -} -uint8_t gnssGetDay() -{ - if (online.gnss == true) - { - if (present.gnss_zedf9p) - { - return (zedGetDay()); - } - else if (present.gnss_um980) - { - return (um980GetDay()); - } - else if (present.gnss_mosaicX5) - { - return (mosaicX5GetDay()); - } - } - return (0); -} -uint8_t gnssGetHour() -{ - if (online.gnss == true) - { - if (present.gnss_zedf9p) - { - return (zedGetHour()); - } - else if (present.gnss_um980) - { - return (um980GetHour()); - } - else if (present.gnss_mosaicX5) - { - return (mosaicX5GetHour()); - } - } - return (0); -} -uint8_t gnssGetMinute() -{ - if (online.gnss == true) - { - if (present.gnss_zedf9p) - { - return (zedGetMinute()); - } - else if (present.gnss_um980) - { - return (um980GetMinute()); - } - else if (present.gnss_mosaicX5) - { - return (mosaicX5GetMinute()); - } - } - return (0); -} -uint8_t gnssGetSecond() -{ - if (online.gnss == true) - { - if (present.gnss_zedf9p) - { - return (zedGetSecond()); - } - else if (present.gnss_um980) - { - return (um980GetSecond()); - } - else if (present.gnss_mosaicX5) - { - return (mosaicX5GetSecond()); - } - } - return (0); -} - -// Limit to two digits -uint8_t gnssGetMillisecond() -{ - if (online.gnss == true) - { - if (present.gnss_zedf9p) - { - return (zedGetMillisecond()); - } - else if (present.gnss_um980) - { - return (um980GetMillisecond()); - } - else if (present.gnss_mosaicX5) - { - return (mosaicX5GetMillisecond()); - } - } - return (0); -} - -// Used in convertGnssTimeToEpoch() -uint32_t gnssGetNanosecond() -{ - if (online.gnss == true) - { - if (present.gnss_zedf9p) - { - return (zedGetNanosecond()); // Return nanosecond fraction of a second of UTC - } - else if (present.gnss_um980) - { - // UM980 does not have nanosecond, but it does have millisecond - return (um980GetMillisecond() * 1000L); // Convert to ns - } - else if (present.gnss_mosaicX5) - { - // mosaicX5 does not have nanosecond, but it does have millisecond (from ToW) - return (mosaicX5GetMillisecond() * 1000L); // Convert to ns - } - } - return (0); -} - -// Return the number of milliseconds since GNSS data was last updated -uint16_t gnssGetFixAgeMilliseconds() -{ - if (online.gnss == true) - { - if (present.gnss_zedf9p) - { - return (zedFixAgeMilliseconds()); - } - else if (present.gnss_um980) - { - return (um980FixAgeMilliseconds()); - } - else if (present.gnss_mosaicX5) - { - return (mosaicX5FixAgeMilliseconds()); - } - } - return (65000); -} - -void gnssPrintModuleInfo() -{ - if (online.gnss == true) - { - if (present.gnss_zedf9p) - { - zedPrintInfo(); - } - else if (present.gnss_um980) - { - um980PrintInfo(); - } - else if (present.gnss_mosaicX5) - { - mosaicX5PrintInfo(); - } - } -} - -void gnssEnableDebugging() -{ - if (online.gnss == true) - { - if (present.gnss_zedf9p) - { - zedEnableDebugging(); - } - else if (present.gnss_um980) - { - um980EnableDebugging(); - } - else if (present.gnss_mosaicX5) - { - ; // TODO - } - } -} -void gnssDisableDebugging() -{ - if (online.gnss == true) - { - if (present.gnss_zedf9p) - { - zedDisableDebugging(); - } - else if (present.gnss_um980) - { - um980DisableDebugging(); - } - else if (present.gnss_mosaicX5) - { - ; // TODO - } - } -} - -void gnssSetTalkerGNGGA() -{ - if (online.gnss == true) - { - if (present.gnss_zedf9p) - { - zedSetTalkerGNGGA(); - } - else if (present.gnss_um980) - { - // TODO um980SetTalkerGNGGA(); - } - else if (present.gnss_mosaicX5) - { - mosaicX5SetTalkerGNGGA(); - } - } -} -void gnssEnableGgaForNtrip() -{ - if (online.gnss == true) - { - if (present.gnss_zedf9p) - { - zedEnableGgaForNtrip(); - } - else if (present.gnss_um980) - { - // TODO um980EnableGgaForNtrip(); - } - else if (present.gnss_mosaicX5) - { - mosaicX5EnableGgaForNtrip(); - } - } -} - -uint16_t gnssRtcmBufferAvailable() -{ - if (online.gnss == true) - { - if (present.gnss_zedf9p) - { - return (zedRtcmBufferAvailable()); - } - else if (present.gnss_um980) - { - // TODO return(um980RtcmBufferAvailable()); - return (0); - } - else if (present.gnss_mosaicX5) - { - // TODO return(mosaicX5RtcmBufferAvailable()); - return (0); - } - } - return (0); -} - -uint16_t gnssRtcmRead(uint8_t *rtcmBuffer, int rtcmBytesToRead) -{ - if (online.gnss == true) - { - if (present.gnss_zedf9p) - { - return (zedRtcmRead(rtcmBuffer, rtcmBytesToRead)); - } - else if (present.gnss_um980) - { - // TODO return(um980RtcmRead(rtcmBuffer, rtcmBytesToRead)); - return (0); - } - else if (present.gnss_mosaicX5) - { - // TODO return(mosaicX5RtcmRead(rtcmBuffer, rtcmBytesToRead)); - return (0); - } - } - return (0); -} - -// Enable all the valid messages for this platform -bool gnssSetMessages(int maxRetries) -{ - if (online.gnss == true) - { - if (present.gnss_zedf9p) - { - return (zedSetMessages(maxRetries)); - } - else if (present.gnss_um980) - { - // We probably don't need this for the UM980 - // TODO return(um980SetMessages(maxRetries)); - return (true); - } - else if (present.gnss_mosaicX5) - { - return(mosaicX5SetMessages(maxRetries)); - } - } - return (false); -} - -bool gnssSetMessagesUsb(int maxRetries) -{ - if (online.gnss == true) - { - if (present.gnss_zedf9p) - { - return (zedSetMessagesUsb(maxRetries)); - } - else if (present.gnss_um980) - { - // We probably don't need this for the UM980 - // TODO return(um980SetMessagesUsb(maxRetries)); - return (true); - } - else if (present.gnss_mosaicX5) - { - return(mosaicX5SetMessagesUsb(maxRetries)); - } - } - return (false); -} - -bool gnssSetConstellations() -{ - if (online.gnss == true) - { - if (present.gnss_zedf9p) - { - return (zedSetConstellations(true)); // Send fully formed setVal list - } - else if (present.gnss_um980) - { - return (um980SetConstellations()); - } - else if (present.gnss_mosaicX5) - { - return (mosaicX5SetConstellations()); - } - } - return (false); -} - -uint16_t gnssFileBufferAvailable() -{ - if (online.gnss == true) - { - if (present.gnss_zedf9p) - { - return (zedFileBufferAvailable()); - } - else if (present.gnss_um980) - { - // TODO return(um980FileBufferAvailable()); - return (0); - } - else if (present.gnss_mosaicX5) - { - return (0); - } - } - - return (0); -} - -uint16_t gnssExtractFileBufferData(uint8_t *fileBuffer, int fileBytesToRead) -{ - if (online.gnss == true) - { - if (present.gnss_zedf9p) - { - return (zedExtractFileBufferData(fileBuffer, fileBytesToRead)); - } - else if (present.gnss_um980) - { - // TODO return(um980FileBufferAvailable()); - return (0); - } - else if (present.gnss_mosaicX5) - { - return (0); - } - } - - return (0); -} - -char *gnssGetId() -{ - if (online.gnss == true) - { - if (present.gnss_zedf9p) - { - return (gnssUniqueId); - } - else if (present.gnss_um980) - { - return (um980GetId()); - } - else if (present.gnss_mosaicX5) - { - return (gnssUniqueId); - } - } - - return ((char *)"\0"); -} - -// Query GNSS for current leap seconds -uint8_t gnssGetLeapSeconds() -{ - if (online.gnss == true) - { - if (leapSeconds == 0) // Check to see if we've already set it - { - if (present.gnss_zedf9p) - { - return (zedGetLeapSeconds()); - } - else if (present.gnss_um980) - { - return (um980GetLeapSeconds()); - } - else if (present.gnss_mosaicX5) - { - return (mosaicX5GetLeapSeconds()); - } - } - } - return (18); // Default to 18 if GNSS is offline -} - -void gnssApplyPointPerfectKeys() -{ - if (present.gnss_zedf9p) - { - zedApplyPointPerfectKeys(); - } - else if (present.gnss_um980) - { - // Taken care of in beginPPL() - } - else if (present.gnss_mosaicX5) - { - // Taken care of in beginPPL() - } -} - -// Return the number of active/enabled messages -uint8_t gnssGetActiveMessageCount() -{ - if (present.gnss_zedf9p) - { - return (zedGetActiveMessageCount()); - } - else if (present.gnss_um980) - { - return (um980GetActiveMessageCount()); - } - else if (present.gnss_mosaicX5) - { - return (mosaicX5GetActiveMessageCount()); - } - return (0); -} - -void gnssMenuMessages() -{ - if (present.gnss_zedf9p) - { - zedMenuMessages(); - } - else if (present.gnss_um980) - { - um980MenuMessages(); - } - else if (present.gnss_mosaicX5) - { - mosaicX5MenuMessages(); - } -} - -void gnssMenuMessageBaseRtcm() -{ - if (present.gnss_zedf9p) - { - zedMenuMessagesSubtype(settings.ubxMessageRatesBase, "RTCM-Base"); - } - else if (present.gnss_um980) - { - um980MenuMessagesSubtype(settings.um980MessageRatesRTCMBase, "RTCMBase"); - } - else if (present.gnss_mosaicX5) - { - mosaicX5MenuMessagesRTCM(false); - } -} - -// Set RTCM for base mode to defaults (1005/1074/1084/1094/1124 1Hz & 1230 0.1Hz) -void gnssBaseRtcmDefault() -{ - if (present.gnss_zedf9p) - { - zedBaseRtcmDefault(); - } - else if (present.gnss_um980) - { - um980BaseRtcmDefault(); - } - else if (present.gnss_mosaicX5) - { - mosaicX5BaseRtcmDefault(); - } -} - -// Reset to Low Bandwidth Link (1074/1084/1094/1124 0.5Hz & 1005/1230 0.1Hz) -void gnssBaseRtcmLowDataRate() -{ - if (present.gnss_zedf9p) - { - zedBaseRtcmLowDataRate(); - } - else if (present.gnss_um980) - { - um980BaseRtcmLowDataRate(); - } - else if (present.gnss_mosaicX5) - { - mosaicX5BaseRtcmLowDataRate(); - } -} - -char *gnssGetRtcmDefaultString() -{ - if (present.gnss_zedf9p) - { - return (zedGetRtcmDefaultString()); - } - else if (present.gnss_um980) - { - return (um980GetRtcmDefaultString()); - } - else if (present.gnss_mosaicX5) - { - return (mosaicX5GetRtcmDefaultString()); - } - return ((char *)"Error"); -} - -char *gnssGetRtcmLowDataRateString() -{ - if (present.gnss_zedf9p) - { - return (zedGetRtcmLowDataRateString()); - } - else if (present.gnss_um980) - { - return (um980GetRtcmLowDataRateString()); - } - else if (present.gnss_mosaicX5) - { - return (mosaicX5GetRtcmLowDataRateString()); - } - return ((char *)"Error"); -} - -float gnssGetSurveyInStartingAccuracy() +//---------------------------------------- +float GNSS::getSurveyInStartingAccuracy() { return (settings.surveyInStartingAccuracy); } -void gnssMenuConstellations() -{ - if (present.gnss_zedf9p) - { - zedMenuConstellations(); - } - else if (present.gnss_um980) - { - um980MenuConstellations(); - } - else if (present.gnss_mosaicX5) - { - mosaicX5MenuConstellations(); - } -} - -bool gnssIsBlocking() -{ - if (online.gnss == true) - { - if (present.gnss_zedf9p) - { - return (false); - } - else if (present.gnss_um980) - { - return (um980IsBlocking()); - } - else if (present.gnss_mosaicX5) - { - return (false); - } - } - return (false); -} - -uint32_t gnssGetRadioBaudRate() -{ - if (online.gnss == true) - { - if (present.gnss_zedf9p) - { - return (zedGetRadioBaudRate()); - } - else if (present.gnss_um980) - { - return (0); // UM980 has no multiplexer - } - else if (present.gnss_mosaicX5) - { - return (mosaicX5GetRadioBaudRate()); - } - } - return (0); -} - -bool gnssSetRadioBaudRate(uint32_t baud) -{ - if (online.gnss == true) - { - if (present.gnss_zedf9p) - { - return (zedSetRadioBaudRate(baud)); - } - else if (present.gnss_um980) - { - return false; // UM980 has no multiplexer - } - else if (present.gnss_mosaicX5) - { - return (mosaicX5SetRadioBaudRate(baud)); - } - } - return false; -} - -uint32_t gnssGetDataBaudRate() +//---------------------------------------- +// Set the minimum satellite signal level for navigation. +//---------------------------------------- +bool GNSS::setMinCno(uint8_t cnoValue) { - if (online.gnss == true) - { - if (present.gnss_zedf9p) - { - return (zedGetDataBaudRate()); - } - else if (present.gnss_um980) - { - return (0); // UM980 has no multiplexer - } - else if (present.gnss_mosaicX5) - { - return (mosaicX5GetDataBaudRate()); - } - } - return (0); -} - -bool gnssSetDataBaudRate(uint32_t baud) -{ - if (online.gnss == true) - { - if (present.gnss_zedf9p) - { - return (zedSetDataBaudRate(baud)); - } - else if (present.gnss_um980) - { - return false; // UM980 has no multiplexer - } - else if (present.gnss_mosaicX5) - { - return (mosaicX5SetDataBaudRate(baud)); - } - } - return false; -} + // Update the setting + settings.minCNO = cnoValue; -bool gnssStandby() -{ - if (online.gnss == true) - { - if (present.gnss_zedf9p) - { - return true; // TODO - this would be a perfect place for Save-On-Shutdown - } - else if (present.gnss_um980) - { - return true; - } - else if (present.gnss_mosaicX5) - { - return mosaicX5Standby(); - } - } - return false; -} - -bool checkGnssNMEARates() -{ - if (online.gnss == true) - { - if (present.gnss_zedf9p) - { - return zedCheckGnssNMEARates(); - } - else if (present.gnss_um980) - { - return false; - } - else if (present.gnss_mosaicX5) - { - return mosaicX5CheckGnssNMEARates(); - } - } - return false; -} - -bool checkGnssPPPRates() -{ - if (online.gnss == true) - { - if (present.gnss_zedf9p) - { - return zedCheckGnssPPPRates(); - } - else if (present.gnss_um980) - { - return false; - } - else if (present.gnss_mosaicX5) - { - return settings.enableLoggingRINEX; - } - } - return false; + // Pass the value to the GNSS receiver + return gnss->setMinCnoRadio(cnoValue); } diff --git a/Firmware/RTK_Everywhere/GNSS_UM980.h b/Firmware/RTK_Everywhere/GNSS_UM980.h new file mode 100644 index 000000000..ed8468dac --- /dev/null +++ b/Firmware/RTK_Everywhere/GNSS_UM980.h @@ -0,0 +1,470 @@ +/*------------------------------------------------------------------------------ +GNSS_UM980.h + + Declarations and definitions for the UM980 GNSS receiver and the GNSS_UM980 class +------------------------------------------------------------------------------*/ + +#ifndef __GNSS_UM980_H__ +#define __GNSS_UM980_H__ + +#ifdef COMPILE_UM980 + +#include //http://librarymanager/All#SparkFun_Unicore_GNSS + +/* + Unicore defaults: + RTCM1006 10 + RTCM1074 1 + RTCM1084 1 + RTCM1094 1 + RTCM1124 1 + RTCM1033 10 +*/ + +// Each constellation will have its config command text, enable, and a visible name +typedef struct +{ + char textName[30]; + char textCommand[5]; +} um980ConstellationCommand; + +// Constellations monitored/used for fix +// Available constellations: GPS, BDS, GLO, GAL, QZSS +// SBAS and IRNSS don't seem to be supported +const um980ConstellationCommand um980ConstellationCommands[] = { + {"BeiDou", "BDS"}, + {"Galileo", "GAL"}, + {"GLONASS", "GLO"}, + {"GPS", "GPS"}, + {"QZSS", "QZSS"}, +}; + +#define MAX_UM980_CONSTELLATIONS (sizeof(um980ConstellationCommands) / sizeof(um980ConstellationCommand)) + +// Struct to describe support messages on the UM980 +// Each message will have the serial command and its default value +typedef struct +{ + const char msgTextName[9]; + const float msgDefaultRate; +} um980Msg; + +// Static array containing all the compatible messages +const um980Msg umMessagesNMEA[] = { + // NMEA + {"GPDTM", 0}, {"GPGBS", 0}, {"GPGGA", 0.5}, {"GPGLL", 0}, {"GPGNS", 0}, + + {"GPGRS", 0}, {"GPGSA", 0.5}, {"GPGST", 0.5}, {"GPGSV", 1}, {"GPRMC", 0.5}, + + {"GPROT", 0}, {"GPTHS", 0}, {"GPVTG", 0}, {"GPZDA", 0}, +}; + +const um980Msg umMessagesRTCM[] = { + + // RTCM + {"RTCM1001", 0}, {"RTCM1002", 0}, {"RTCM1003", 0}, {"RTCM1004", 0}, {"RTCM1005", 1}, + {"RTCM1006", 0}, {"RTCM1007", 0}, {"RTCM1009", 0}, {"RTCM1010", 0}, + + {"RTCM1011", 0}, {"RTCM1012", 0}, {"RTCM1013", 0}, {"RTCM1019", 0}, + + {"RTCM1020", 0}, + + {"RTCM1033", 10}, + + {"RTCM1042", 0}, {"RTCM1044", 0}, {"RTCM1045", 0}, {"RTCM1046", 0}, + + {"RTCM1071", 0}, {"RTCM1072", 0}, {"RTCM1073", 0}, {"RTCM1074", 1}, {"RTCM1075", 0}, + {"RTCM1076", 0}, {"RTCM1077", 0}, + + {"RTCM1081", 0}, {"RTCM1082", 0}, {"RTCM1083", 0}, {"RTCM1084", 1}, {"RTCM1085", 0}, + {"RTCM1086", 0}, {"RTCM1087", 0}, + + {"RTCM1091", 0}, {"RTCM1092", 0}, {"RTCM1093", 0}, {"RTCM1094", 1}, {"RTCM1095", 0}, + {"RTCM1096", 0}, {"RTCM1097", 0}, + + {"RTCM1104", 0}, + + {"RTCM1111", 0}, {"RTCM1112", 0}, {"RTCM1113", 0}, {"RTCM1114", 0}, {"RTCM1115", 0}, + {"RTCM1116", 0}, {"RTCM1117", 0}, + + {"RTCM1121", 0}, {"RTCM1122", 0}, {"RTCM1123", 0}, {"RTCM1124", 1}, {"RTCM1125", 0}, + {"RTCM1126", 0}, {"RTCM1127", 0}, +}; + +#define MAX_UM980_NMEA_MSG (sizeof(umMessagesNMEA) / sizeof(um980Msg)) +#define MAX_UM980_RTCM_MSG (sizeof(umMessagesRTCM) / sizeof(um980Msg)) + +enum um980_Models +{ + UM980_DYN_MODEL_SURVEY = 0, + UM980_DYN_MODEL_UAV, + UM980_DYN_MODEL_AUTOMOTIVE, +}; + +class GNSS_UM980 : GNSS +{ + private: + + UM980 * _um980; // Library class instance + + protected: + + bool configureOnce(); + + // Setup the general configuration of the GNSS + // Not Rover or Base specific (ie, baud rates) + // Outputs: + // Returns true if successfully configured and false upon failure + bool configureRadio(); + + // Turn off all NMEA and RTCM + void disableAllOutput(); + + // Disable all output, then re-enable + void disableRTCM(); + + // Turn on all the enabled NMEA messages on COM3 + bool enableNMEA(); + + // Turn on all the enabled RTCM Rover messages on COM3 + bool enableRTCMRover(); + + // Turn on all the enabled RTCM Base messages on COM3 + bool enableRTCMBase(); + + uint8_t getActiveNmeaMessageCount(); + + uint8_t getActiveRtcmMessageCount(); + + // Given the name of an NMEA message, return the array number + uint8_t getNmeaMessageNumberByName(const char *msgName); + + // Given the name of an RTCM message, return the array number + uint8_t getRtcmMessageNumberByName(const char *msgName); + + // Returns true if the device is in Rover mode + // Currently the only two modes are Rover or Base + bool inRoverMode(); + + // Return true if the GPGGA message is active + bool isGgaActive(); + + // Given a sub type (ie "RTCM", "NMEA") present menu showing messages with this subtype + // Controls the messages that get broadcast over Bluetooth and logged (if enabled) + void menuMessagesSubtype(float *localMessageRate, const char *messageType); + + // Set the baud rate on the GNSS port that interfaces between the ESP32 and the GNSS + // Inputs: + // baudRate: The desired baudrate + bool setBaudRateCOM3(uint32_t baudRate); + + bool setHighAccuracyService(bool enableGalileoHas); + + // Set the minimum satellite signal level for navigation. + bool setMinCnoRadio (uint8_t cnoValue); + + bool setMultipathMitigation(bool enableMultipathMitigation); + + public: + + // If we have decryption keys, configure module + // Note: don't check online.lband_neo here. We could be using ip corrections + void applyPointPerfectKeys(); + + // Set RTCM for base mode to defaults (1005/1074/1084/1094/1124 1Hz & 1230 0.1Hz) + void baseRtcmDefault(); + + // Reset to Low Bandwidth Link (1074/1084/1094/1124 0.5Hz & 1005/1230 0.1Hz) + void baseRtcmLowDataRate(); + + // Connect to GNSS and identify particulars + void begin(); + + // Setup TM2 time stamp input as need + // Outputs: + // Returns true when an external event occurs and false if no event + bool beginExternalEvent(); + + // Setup the timepulse output on the PPS pin for external triggering + // Outputs + // Returns true if the pin was successfully setup and false upon + // failure + bool beginPPS(); + + bool checkNMEARates(); + + bool checkPPPRates(); + + // Configure the Base + // Outputs: + // Returns true if successfully configured and false upon failure + bool configureBase(); + + // Configure specific aspects of the receiver for NTP mode + bool configureNtpMode(); + + // Configure the Rover + // Outputs: + // Returns true if successfully configured and false upon failure + bool configureRover(); + + void debuggingDisable(); + + void debuggingEnable(); + + void enableGgaForNtrip(); + + // Enable RTCM 1230. This is the GLONASS bias sentence and is transmitted + // even if there is no GPS fix. We use it to test serial output. + bool enableRTCMTest(); + + // Restore the GNSS to the factory settings + void factoryReset(); + + uint16_t fileBufferAvailable(); + + uint16_t fileBufferExtractData(uint8_t *fileBuffer, int fileBytesToRead); + + // Start the base using fixed coordinates + // Outputs: + // Returns true if successfully started and false upon failure + bool fixedBaseStart(); + + // Return the number of active/enabled messages + uint8_t getActiveMessageCount(); + + // Get the altitude + // Outputs: + // Returns the altitude in meters or zero if the GNSS is offline + double getAltitude(); + + // Returns the carrier solution or zero if not online + uint8_t getCarrierSolution(); + + uint32_t getDataBaudRate(); + + // Returns the day number or zero if not online + uint8_t getDay(); + + // Return the number of milliseconds since GNSS data was last updated + uint16_t getFixAgeMilliseconds(); + + // Returns the fix type or zero if not online + uint8_t getFixType(); + + // Returns the hours of 24 hour clock or zero if not online + uint8_t getHour(); + + // Get the horizontal position accuracy + // Outputs: + // Returns the horizontal position accuracy or zero if offline + float getHorizontalAccuracy(); + + const char * getId(); + + // Get the latitude value + // Outputs: + // Returns the latitude value or zero if not online + double getLatitude(); + + // Query GNSS for current leap seconds + uint8_t getLeapSeconds(); + + // Get the longitude value + // Outputs: + // Returns the longitude value or zero if not online + double getLongitude(); + + // Returns two digits of milliseconds or zero if not online + uint8_t getMillisecond(); + + // Get the minimum satellite signal level for navigation. + uint8_t getMinCno(); + + // Returns minutes or zero if not online + uint8_t getMinute(); + + // Returns month number or zero if not online + uint8_t getMonth(); + + // Returns nanoseconds or zero if not online + uint32_t getNanosecond(); + + uint32_t getRadioBaudRate(); + + // Returns the seconds between solutions + double getRateS(); + + const char * getRtcmDefaultString(); + + const char * getRtcmLowDataRateString(); + + // Returns the number of satellites in view or zero if offline + uint8_t getSatellitesInView(); + + // Returns seconds or zero if not online + uint8_t getSecond(); + + // Get the survey-in mean accuracy + // Outputs: + // Returns the mean accuracy or zero (0) + float getSurveyInMeanAccuracy(); + + // Return the number of seconds the survey-in process has been running + int getSurveyInObservationTime(); + + float getSurveyInStartingAccuracy(); + + // Returns timing accuracy or zero if not online + uint32_t getTimeAccuracy(); + + // Returns full year, ie 2023, not 23. + uint16_t getYear(); + + bool isBlocking(); + + // Date is confirmed once we have GNSS fix + bool isConfirmedDate(); + + // Date is confirmed once we have GNSS fix + bool isConfirmedTime(); + + // Return true if GNSS receiver has a higher quality DGPS fix than 3D + bool isDgpsFixed(); + + // Some functions (L-Band area frequency determination) merely need + // to know if we have a valid fix, not what type of fix + // This function checks to see if the given platform has reached + // sufficient fix type to be considered valid + bool isFixed(); + + // Used in tpISR() for time pulse synchronization + bool isFullyResolved(); + + bool isPppConverged(); + + bool isPppConverging(); + + // Some functions (L-Band area frequency determination) merely need + // to know if we have an RTK Fix. This function checks to see if the + // given platform has reached sufficient fix type to be considered valid + bool isRTKFix(); + + // Some functions (L-Band area frequency determination) merely need + // to know if we have an RTK Float. This function checks to see if + // the given platform has reached sufficient fix type to be considered + // valid + bool isRTKFloat(); + + // Determine if the survey-in operation is complete + // Outputs: + // Returns true if the survey-in operation is complete and false + // if the operation is still running + bool isSurveyInComplete(); + + // Date will be valid if the RTC is reporting (regardless of GNSS fix) + bool isValidDate(); + + // Time will be valid if the RTC is reporting (regardless of GNSS fix) + bool isValidTime(); + + // Controls the constellations that are used to generate a fix and logged + void menuConstellations(); + + void menuMessageBaseRtcm(); + + // Control the messages that get broadcast over Bluetooth and logged (if enabled) + void menuMessages(); + + // Print the module type and firmware version + void printModuleInfo(); + + // Send correction data to the GNSS + // Inputs: + // dataToSend: Address of a buffer containing the data + // dataLength: The number of valid data bytes in the buffer + // Outputs: + // Returns the number of correction data bytes written + int pushRawData(uint8_t *dataToSend, int dataLength); + + uint16_t rtcmBufferAvailable(); + + // If LBand is being used, ignore any RTCM that may come in from the GNSS + void rtcmOnGnssDisable(); + + // If L-Band is available, but encrypted, allow RTCM through other sources (radio, ESP-Now) to GNSS receiver + void rtcmOnGnssEnable(); + + uint16_t rtcmRead(uint8_t *rtcmBuffer, int rtcmBytesToRead); + + // Save the current configuration + // Outputs: + // Returns true when the configuration was saved and false upon failure + bool saveConfiguration(); + + // Set the baud rate on the GNSS port that interfaces between the ESP32 and the GNSS + // This just sets the GNSS side + // Used during Bluetooth testing + // Inputs: + // baudRate: The desired baudrate + bool setBaudrate(uint32_t baudRate); + + // Enable all the valid constellations and bands for this platform + bool setConstellations(); + + bool setDataBaudRate(uint32_t baud); + + // Set the elevation in degrees + // Inputs: + // elevationDegrees: The elevation value in degrees + bool setElevation(uint8_t elevationDegrees); + + // Enable all the valid messages for this platform + bool setMessages(int maxRetries); + + // Enable all the valid messages for this platform over the USB port + bool setMessagesUsb(int maxRetries); + + // Set the dynamic model to use for RTK + // Inputs: + // modelNumber: Number of the model to use, provided by radio library + bool setModel(uint8_t modelNumber); + + bool setRadioBaudRate(uint32_t baud); + + // Specify the interval between solutions + // Inputs: + // secondsBetweenSolutions: Number of seconds between solutions + // Outputs: + // Returns true if the rate was successfully set and false upon + // failure + bool setRate(double secondsBetweenSolutions); + + bool setTalkerGNGGA(); + + // Hotstart GNSS to try to get RTK lock + bool softwareReset(); + + bool standby(); + + // Reset the survey-in operation + // Outputs: + // Returns true if the survey-in operation was reset successfully + // and false upon failure + bool surveyInReset(); + + // Start the survey-in operation + // Outputs: + // Return true if successful and false upon failure + bool surveyInStart(); + + // If we have received serial data from the UM980 outside of the Unicore library (ie, from processUart1Message task) + // we can pass data back into the Unicore library to allow it to update its own variables + void unicoreHandler(uint8_t *buffer, int length); + + // Poll routine to update the GNSS state + void update(); +}; + +#endif // COMPILE_UM980 +#endif // __GNSS_UM980_H__ diff --git a/Firmware/RTK_Everywhere/GNSS_UM980.ino b/Firmware/RTK_Everywhere/GNSS_UM980.ino new file mode 100644 index 000000000..347418ddd --- /dev/null +++ b/Firmware/RTK_Everywhere/GNSS_UM980.ino @@ -0,0 +1,2012 @@ +/*------------------------------------------------------------------------------ +GNSS_UM980.ino + + Implementation of the GNSS_UM980 class + + IM19 reads in binary+NMEA from the UM980 and passes out binary with tilt-corrected lat/long/alt + to the ESP32. + + The ESP32 reads in binary from the IM19. + + The ESP32 reads in binary and NMEA from the UM980 and passes that data over Bluetooth. + If tilt compensation is activated, the ESP32 intercepts the NMEA from the UM980 and + injects the new tilt-compensated data, previously read from the IM19. +------------------------------------------------------------------------------*/ + +#ifdef COMPILE_UM980 + +//---------------------------------------- +// If we have decryption keys, configure module +// Note: don't check online.lband_neo here. We could be using ip corrections +//---------------------------------------- +void GNSS_UM980::applyPointPerfectKeys() +{ + // Taken care of in beginPPL() +} + +//---------------------------------------- +// Set RTCM for base mode to defaults (1005/1074/1084/1094/1124 1Hz & 1033 0.1Hz) +//---------------------------------------- +void GNSS_UM980::baseRtcmDefault() +{ + // Reset RTCM rates to defaults + for (int x = 0; x < MAX_UM980_RTCM_MSG; x++) + settings.um980MessageRatesRTCMBase[x] = umMessagesRTCM[x].msgDefaultRate; +} + +//---------------------------------------- +// Reset to Low Bandwidth Link (1074/1084/1094/1124 0.5Hz & 1005/1033 0.1Hz) +//---------------------------------------- +void GNSS_UM980::baseRtcmLowDataRate() +{ + // Zero out everything + for (int x = 0; x < MAX_UM980_RTCM_MSG; x++) + settings.um980MessageRatesRTCMBase[x] = 0; + + settings.um980MessageRatesRTCMBase[getRtcmMessageNumberByName("RTCM1005")] = + 10; // 1005 0.1Hz - Exclude antenna height + settings.um980MessageRatesRTCMBase[getRtcmMessageNumberByName("RTCM1074")] = 2; // 1074 0.5Hz + settings.um980MessageRatesRTCMBase[getRtcmMessageNumberByName("RTCM1084")] = 2; // 1084 0.5Hz + settings.um980MessageRatesRTCMBase[getRtcmMessageNumberByName("RTCM1094")] = 2; // 1094 0.5Hz + settings.um980MessageRatesRTCMBase[getRtcmMessageNumberByName("RTCM1124")] = 2; // 1124 0.5Hz + settings.um980MessageRatesRTCMBase[getRtcmMessageNumberByName("RTCM1033")] = 10; // 1033 0.1Hz +} + +//---------------------------------------- +// Connect to GNSS and identify particulars +//---------------------------------------- +void GNSS_UM980::begin() +{ + // During identifyBoard(), the GNSS UART and DR pins are set + + // The GNSS UART is already started. We can now pass it to the library. + if (serialGNSS == nullptr) + { + systemPrintln("GNSS UART not started"); + return; + } + + // Instantiate the library + _um980 = new UM980(); + + // Turn on/off debug messages + if (settings.debugGnss) + debuggingEnable(); + + // In order to reduce UM980 configuration time, the UM980 library blocks the start of BESTNAV and RECTIME until 3D + // fix is achieved However, if all NMEA messages are disabled, the UM980 will never detect a 3D fix. + if (isGgaActive()) + // If NMEA GPGGA is turned on, suppress BESTNAV messages until GPGGA reports a 3D fix + _um980->disableBinaryBeforeFix(); + else + // If NMEA GPGGA is turned off, enable BESTNAV messages at power on which may lead to longer UM980 configuration + // times + _um980->enableBinaryBeforeFix(); + + if (_um980->begin(*serialGNSS) == false) // Give the serial port over to the library + { + if (settings.debugGnss) + systemPrintln("GNSS Failed to begin. Trying again."); + + // Try again with power on delay + delay(1000); + if (_um980->begin(*serialGNSS) == false) + { + systemPrintln("GNSS offline"); + displayGNSSFail(1000); + return; + } + } + systemPrintln("GNSS UM980 online"); + + // Shortly after reset, the UM980 responds to the VERSIONB command with OK but doesn't report version information + if (ENABLE_DEVELOPER == false) + delay(2000); // 1s fails, 2s ok + + // Check firmware version and print info + printModuleInfo(); + + // Shortly after reset, the UM980 responds to the VERSIONB command with OK but doesn't report version information + snprintf(gnssFirmwareVersion, sizeof(gnssFirmwareVersion), "%s", _um980->getVersion()); + + // getVersion returns the "Build" "7923". I think we probably need the "R4.10" which preceeds Build? TODO + if (sscanf(gnssFirmwareVersion, "%d", &gnssFirmwareVersionInt) != 1) + gnssFirmwareVersionInt = 99; + + snprintf(gnssUniqueId, sizeof(gnssUniqueId), "%s", _um980->getID()); + + online.gnss = true; +} + +//---------------------------------------- +// Setup the timepulse output on the PPS pin for external triggering +// Setup TM2 time stamp input as need +//---------------------------------------- +bool GNSS_UM980::beginExternalEvent() +{ + // UM980 Event signal not exposed + return (false); +} + +//---------------------------------------- +// Setup the timepulse output on the PPS pin for external triggering +//---------------------------------------- +bool GNSS_UM980::beginPPS() +{ + // UM980 PPS signal not exposed + return (false); +} + +//---------------------------------------- +bool GNSS_UM980::checkNMEARates() +{ + return false; +} + +//---------------------------------------- +bool GNSS_UM980::checkPPPRates() +{ + return false; +} + +//---------------------------------------- +// Configure specific aspects of the receiver for base mode +//---------------------------------------- +bool GNSS_UM980::configureBase() +{ + /* + Disable all messages + Start base + Enable RTCM Base messages + Enable NMEA messages + */ + + if (online.gnss == false) + { + systemPrintln("GNSS not online"); + return (false); + } + + disableAllOutput(); + + bool response = true; + + // Set the dynamic mode. This will cancel any base averaging mode and is needed + // to allow a freshly started device to settle in regular GNSS reception mode before issuing + // um980BaseAverageStart(). + response &= setModel(settings.dynamicModel); + + response &= setMultipathMitigation(settings.enableMultipathMitigation); + + response &= setHighAccuracyService(settings.enableGalileoHas); + + response &= enableRTCMBase(); // Only turn on messages, do not turn off messages. We assume the caller has + // UNLOG or similar. + + // Only turn on messages, do not turn off messages. We assume the caller has UNLOG or similar. + response &= enableNMEA(); + + // Save the current configuration into non-volatile memory (NVM) + // We don't need to re-configure the UM980 at next boot + bool settingsWereSaved = _um980->saveConfiguration(); + if (settingsWereSaved) + settings.updateGNSSSettings = false; + + if (response == false) + { + systemPrintln("UM980 Base failed to configure"); + } + + if (settings.debugGnss) + systemPrintln("UM980 Base configured"); + + return (response); +} + +//---------------------------------------- +bool GNSS_UM980::configureOnce() +{ + /* + Disable all message traffic + Set COM port baud rates, + UM980 COM1 - Direct to USB, 115200 + UM980 COM2 - To IMU. From settings. + UM980 COM3 - BT, config and LoRa Radio. Configured for 115200 from begin(). + Set minCNO + Set elevationAngle + Set Constellations + Set messages + Enable selected NMEA messages on COM3 + Enable selected RTCM messages on COM3 +*/ + + if (settings.debugGnss) + debuggingEnable(); // Print all debug to Serial + + disableAllOutput(); // Disable COM1/2/3 + + bool response = true; + response &= _um980->setPortBaudrate("COM1", 115200); // COM1 is connected to switch, then USB + response &= _um980->setPortBaudrate("COM2", 115200); // COM2 is connected to the IMU + response &= _um980->setPortBaudrate("COM3", 115200); // COM3 is connected to the switch, then ESP32 + + // For now, let's not change the baud rate of the interface. We'll be using the default 115200 for now. + response &= setBaudRateCOM3(settings.dataPortBaud); // COM3 is connected to ESP UART2 + + // Enable PPS signal with a width of 200ms, and a period of 1 second + response &= _um980->enablePPS(200000, 1000); // widthMicroseconds, periodMilliseconds + + response &= setElevation(settings.minElev); // UM980 default is 5 degrees. Our default is 10. + + response &= setMinCnoRadio(settings.minCNO); + + response &= setConstellations(); + + if (_um980->isConfigurationPresent("CONFIG SIGNALGROUP 2") == false) + { + if (_um980->sendCommand("CONFIG SIGNALGROUP 2") == false) + systemPrintln("Signal group 2 command failed"); + else + { + systemPrintln("Enabling additional reception on UM980. This can take a few seconds."); + + while (1) + { + delay(1000); // Wait for device to reboot + if (_um980->isConnected()) + break; + else + systemPrintln("UM980 rebooting"); + } + + systemPrintln("UM980 has completed reboot."); + } + } + + if (response) + { + online.gnss = true; // If we failed before, mark as online now + + systemPrintln("UM980 configuration updated"); + + // Save the current configuration into non-volatile memory (NVM) + // We don't need to re-configure the UM980 at next boot + bool settingsWereSaved = _um980->saveConfiguration(); + if (settingsWereSaved) + settings.updateGNSSSettings = false; + } + else + online.gnss = false; // Take it offline + + return (response); +} + +//---------------------------------------- +// Configure specific aspects of the receiver for NTP mode +//---------------------------------------- +bool GNSS_UM980::configureNtpMode() +{ + return false; +} + +//---------------------------------------- +// Setup the u-blox module for any setup (base or rover) +// In general we check if the setting is incorrect before writing it. Otherwise, the set commands have, on rare +// occasion, become corrupt. The worst is when the I2C port gets turned off or the I2C address gets borked. +//---------------------------------------- +bool GNSS_UM980::configureRadio() +{ + // Skip configuring the UM980 if no new changes are necessary + if (settings.updateGNSSSettings == false) + { + systemPrintln("UM980 configuration maintained"); + return (true); + } + + for (int x = 0; x < 3; x++) + { + if (configureOnce()) + return (true); + + // If we fail, reset UM980 + systemPrintln("Resetting UM980 to complete configuration"); + + um980Reset(); + delay(500); + um980Boot(); + delay(500); + } + + systemPrintln("UM980 failed to configure"); + return (false); +} + +//---------------------------------------- +// Configure specific aspects of the receiver for rover mode +//---------------------------------------- +bool GNSS_UM980::configureRover() +{ + /* + Disable all message traffic + Cancel any survey-in modes + Set mode to Rover + dynamic model + Set minElevation + Enable RTCM messages on COM3 + Enable NMEA on COM3 + */ + if (online.gnss == false) + { + systemPrintln("GNSS not online"); + return (false); + } + + disableAllOutput(); + + bool response = true; + + response &= setModel(settings.dynamicModel); // This will cancel any base averaging mode + + response &= setElevation(settings.minElev); // UM980 default is 5 degrees. Our default is 10. + + response &= setMultipathMitigation(settings.enableMultipathMitigation); + + response &= setHighAccuracyService(settings.enableGalileoHas); + + // Configure UM980 to output binary reports out COM2, connected to IM19 COM3 + response &= _um980->sendCommand("BESTPOSB COM2 0.2"); // 5Hz + response &= _um980->sendCommand("PSRVELB COM2 0.2"); + + // Configure UM980 to output NMEA reports out COM2, connected to IM19 COM3 + response &= _um980->setNMEAPortMessage("GPGGA", "COM2", 0.2); // 5Hz + + // Enable the NMEA sentences and RTCM on COM3 last. This limits the traffic on the config + // interface port during config. + + // Only turn on messages, do not turn off messages. We assume the caller has UNLOG or similar. + response &= enableRTCMRover(); + // TODO consider reducing the GSV sentence to 1/4 of the GPGGA setting + + // Only turn on messages, do not turn off messages. We assume the caller has UNLOG or similar. + response &= enableNMEA(); + + // Save the current configuration into non-volatile memory (NVM) + // We don't need to re-configure the UM980 at next boot + bool settingsWereSaved = _um980->saveConfiguration(); + if (settingsWereSaved) + settings.updateGNSSSettings = false; + + if (response == false) + { + systemPrintln("UM980 Rover failed to configure"); + } + + return (response); +} + +//---------------------------------------- +void GNSS_UM980::debuggingDisable() +{ + if (online.gnss) + _um980->disableDebugging(); +} + +//---------------------------------------- +void GNSS_UM980::debuggingEnable() +{ + if (online.gnss) + { + _um980->enableDebugging(); // Print all debug to Serial + _um980->enablePrintRxMessages(); // Print incoming processed messages from SEMP + } +} + +//---------------------------------------- +// Turn off all NMEA and RTCM +void GNSS_UM980::disableAllOutput() +{ + if (settings.debugGnss) + systemPrintln("UM980 disable output"); + + // Turn off local noise before moving to other ports + _um980->disableOutput(); + + // Re-attempt as necessary + for (int x = 0; x < 3; x++) + { + bool response = true; + response &= _um980->disableOutputPort("COM1"); + response &= _um980->disableOutputPort("COM2"); + response &= _um980->disableOutputPort("COM3"); + if (response) + break; + } +} + +//---------------------------------------- +// Disable all output, then re-enable +//---------------------------------------- +void GNSS_UM980::disableRTCM() +{ + disableAllOutput(); + enableNMEA(); +} + +//---------------------------------------- +void GNSS_UM980::enableGgaForNtrip() +{ + // TODO um980EnableGgaForNtrip(); +} + +//---------------------------------------- +// Turn on all the enabled NMEA messages on COM3 +//---------------------------------------- +bool GNSS_UM980::enableNMEA() +{ + bool response = true; + bool gpggaEnabled = false; + bool gpzdaEnabled = false; + + for (int messageNumber = 0; messageNumber < MAX_UM980_NMEA_MSG; messageNumber++) + { + // Only turn on messages, do not turn off messages set to 0. This saves on command sending. We assume the caller + // has UNLOG or similar. + if (settings.um980MessageRatesNMEA[messageNumber] > 0) + { + if (_um980->setNMEAPortMessage(umMessagesNMEA[messageNumber].msgTextName, "COM3", + settings.um980MessageRatesNMEA[messageNumber]) == false) + { + if (settings.debugGnss) + systemPrintf("Enable NMEA failed at messageNumber %d %s.\r\n", messageNumber, + umMessagesNMEA[messageNumber].msgTextName); + response &= false; // If any one of the commands fails, report failure overall + } + + // If we are using IP based corrections, we need to send local data to the PPL + // The PPL requires being fed GPGGA/ZDA, and RTCM1019/1020/1042/1046 + if (strstr(settings.pointPerfectKeyDistributionTopic, "/ip") != nullptr) + { + // Mark PPL requied messages as enabled if rate > 0 + if (strcmp(umMessagesNMEA[messageNumber].msgTextName, "GPGGA") == 0) + gpggaEnabled = true; + if (strcmp(umMessagesNMEA[messageNumber].msgTextName, "GPZDA") == 0) + gpzdaEnabled = true; + } + } + } + + if (settings.enablePointPerfectCorrections) + { + // Force on any messages that are needed for PPL + if (gpggaEnabled == false) + response &= _um980->setNMEAPortMessage("GPGGA", "COM3", 1); + if (gpzdaEnabled == false) + response &= _um980->setNMEAPortMessage("GPZDA", "COM3", 1); + } + + return (response); +} + +//---------------------------------------- +// Turn on all the enabled RTCM Base messages on COM3 +//---------------------------------------- +bool GNSS_UM980::enableRTCMBase() +{ + bool response = true; + + for (int messageNumber = 0; messageNumber < MAX_UM980_RTCM_MSG; messageNumber++) + { + // Only turn on messages, do not turn off messages set to 0. This saves on command sending. We assume the caller + // has UNLOG or similar. + if (settings.um980MessageRatesRTCMBase[messageNumber] > 0) + { + if (_um980->setRTCMPortMessage(umMessagesRTCM[messageNumber].msgTextName, "COM3", + settings.um980MessageRatesRTCMBase[messageNumber]) == false) + { + if (settings.debugGnss) + systemPrintf("Enable RTCM failed at messageNumber %d %s.", messageNumber, + umMessagesRTCM[messageNumber].msgTextName); + response &= false; // If any one of the commands fails, report failure overall + } + } + } + + return (response); +} + +//---------------------------------------- +// Turn on all the enabled RTCM Rover messages on COM3 +//---------------------------------------- +bool GNSS_UM980::enableRTCMRover() +{ + bool response = true; + bool rtcm1019Enabled = false; + bool rtcm1020Enabled = false; + bool rtcm1042Enabled = false; + bool rtcm1046Enabled = false; + + for (int messageNumber = 0; messageNumber < MAX_UM980_RTCM_MSG; messageNumber++) + { + // Only turn on messages, do not turn off messages set to 0. This saves on command sending. We assume the caller + // has UNLOG or similar. + if (settings.um980MessageRatesRTCMRover[messageNumber] > 0) + { + if (_um980->setRTCMPortMessage(umMessagesRTCM[messageNumber].msgTextName, "COM3", + settings.um980MessageRatesRTCMRover[messageNumber]) == false) + { + if (settings.debugGnss) + systemPrintf("Enable RTCM failed at messageNumber %d %s.", messageNumber, + umMessagesRTCM[messageNumber].msgTextName); + response &= false; // If any one of the commands fails, report failure overall + } + + // If we are using IP based corrections, we need to send local data to the PPL + // The PPL requires being fed GPGGA/ZDA, and RTCM1019/1020/1042/1046 + if (settings.enablePointPerfectCorrections) + { + // Mark PPL required messages as enabled if rate > 0 + if (strcmp(umMessagesNMEA[messageNumber].msgTextName, "RTCM1019") == 0) + rtcm1019Enabled = true; + if (strcmp(umMessagesNMEA[messageNumber].msgTextName, "RTCM1020") == 0) + rtcm1020Enabled = true; + if (strcmp(umMessagesNMEA[messageNumber].msgTextName, "RTCM1042") == 0) + rtcm1042Enabled = true; + if (strcmp(umMessagesNMEA[messageNumber].msgTextName, "RTCM1046") == 0) + rtcm1046Enabled = true; + } + } + } + + if (settings.enablePointPerfectCorrections) + { + // Force on any messages that are needed for PPL + if (rtcm1019Enabled == false) + response &= _um980->setRTCMPortMessage("RTCM1019", "COM3", 1); + if (rtcm1020Enabled == false) + response &= _um980->setRTCMPortMessage("RTCM1020", "COM3", 1); + if (rtcm1042Enabled == false) + response &= _um980->setRTCMPortMessage("RTCM1042", "COM3", 1); + if (rtcm1046Enabled == false) + response &= _um980->setRTCMPortMessage("RTCM1046", "COM3", 1); + } + + return (response); +} + +//---------------------------------------- +// Enable RTCM 1230. This is the GLONASS bias sentence and is transmitted +// even if there is no GPS fix. We use it to test serial output. +//---------------------------------------- +bool GNSS_UM980::enableRTCMTest() +{ + // There is no data port on devices with the UM980 + return false; +} + +//---------------------------------------- +// Restore the GNSS to the factory settings +//---------------------------------------- +void GNSS_UM980::factoryReset() +{ + if (online.gnss) + { + _um980->factoryReset(); + + // systemPrintln("Waiting for UM980 to reboot"); + // while (1) + // { + // delay(1000); //Wait for device to reboot + // if (_um980->isConnected()) break; + // else systemPrintln("Device still rebooting"); + // } + // systemPrintln("UM980 has been factory reset"); + } +} + +//---------------------------------------- +uint16_t GNSS_UM980::fileBufferAvailable() +{ + // TODO return(um980FileBufferAvailable()); + return (0); +} + +//---------------------------------------- +uint16_t GNSS_UM980::fileBufferExtractData(uint8_t *fileBuffer, int fileBytesToRead) +{ + // TODO return(um980FileBufferAvailable()); + return (0); +} + +//---------------------------------------- +// Start the base using fixed coordinates +//---------------------------------------- +bool GNSS_UM980::fixedBaseStart() +{ + bool response = true; + + if (online.gnss == false) + return (false); + + if (settings.fixedBaseCoordinateType == COORD_TYPE_ECEF) + { + _um980->setModeBaseECEF(settings.fixedEcefX, settings.fixedEcefY, settings.fixedEcefZ); + } + else if (settings.fixedBaseCoordinateType == COORD_TYPE_GEODETIC) + { + // Add height of instrument (HI) to fixed altitude + // https://www.e-education.psu.edu/geog862/node/1853 + // For example, if HAE is at 100.0m, + 2m stick + 73mm APC = 102.073 + float totalFixedAltitude = + settings.fixedAltitude + ((settings.antennaHeight_mm + settings.antennaPhaseCenter_mm) / 1000.0); + + _um980->setModeBaseGeodetic(settings.fixedLat, settings.fixedLong, totalFixedAltitude); + } + + return (response); +} + +//---------------------------------------- +// Return the number of active/enabled messages +//---------------------------------------- +uint8_t GNSS_UM980::getActiveMessageCount() +{ + uint8_t count = 0; + + count += getActiveNmeaMessageCount(); + count += getActiveRtcmMessageCount(); + return (count); +} + +//---------------------------------------- +uint8_t GNSS_UM980::getActiveNmeaMessageCount() +{ + uint8_t count = 0; + + for (int x = 0; x < MAX_UM980_NMEA_MSG; x++) + if (settings.um980MessageRatesNMEA[x] > 0) + count++; + + return (count); +} + +//---------------------------------------- +uint8_t GNSS_UM980::getActiveRtcmMessageCount() +{ + uint8_t count = 0; + + // Determine which state we are in + if (inRoverMode()) + { + for (int x = 0; x < MAX_UM980_RTCM_MSG; x++) + if (settings.um980MessageRatesRTCMRover[x] > 0) + count++; + } + else + { + for (int x = 0; x < MAX_UM980_RTCM_MSG; x++) + if (settings.um980MessageRatesRTCMBase[x] > 0) + count++; + } + + return (count); +} + +//---------------------------------------- +// Returns the altitude in meters or zero if the GNSS is offline +//---------------------------------------- +double GNSS_UM980::getAltitude() +{ + if (online.gnss) + return (_um980->getAltitude()); + return (0); +} + +//---------------------------------------- +// Returns the carrier solution or zero if not online +//---------------------------------------- +uint8_t GNSS_UM980::getCarrierSolution() +{ + if (online.gnss) + // 0 = Solution computed + // 1 = Insufficient observation + // 3 = No convergence, + // 4 = Covariance trace + return (_um980->getSolutionStatus()); + return 0; +} + +//---------------------------------------- +uint32_t GNSS_UM980::getDataBaudRate() +{ + return (0); // UM980 has no multiplexer +} + +//---------------------------------------- +// Returns the day number or zero if not online +//---------------------------------------- +uint8_t GNSS_UM980::getDay() +{ + if (online.gnss) + return (_um980->getDay()); + return 0; +} + +//---------------------------------------- +// Return the number of milliseconds since GNSS data was last updated +//---------------------------------------- +uint16_t GNSS_UM980::getFixAgeMilliseconds() +{ + if (online.gnss) + return (_um980->getFixAgeMilliseconds()); + return 0; +} + +//---------------------------------------- +// Returns the fix type or zero if not online +//---------------------------------------- +uint8_t GNSS_UM980::getFixType() +{ + if (online.gnss) + // 0 = None + // 1 = FixedPos + // 8 = DopplerVelocity, + // 16 = 3D Fix (Single) + // 17 = Pseudorange differential solution + // 18 = SBAS, 32 = L1 float + // 33 = Ionosphere-free float solution + // 34 = Narrow-land float solution + // 48 = L1 fixed solution + // 49 = RTK Float (Presumed) (Wide-lane fixed solution) + // 50 = RTK Fixed (Narrow-lane fixed solution) + // 68 = Precise Point Positioning solution converging + // 69 = Precise Point Positioning + return (_um980->getPositionType()); + return 0; +} + +//---------------------------------------- +// Get the horizontal position accuracy +// Returns the horizontal position accuracy or zero if offline +//---------------------------------------- +float GNSS_UM980::getHorizontalAccuracy() +{ + if (online.gnss) + { + float latitudeDeviation = _um980->getLatitudeDeviation(); + float longitudeDeviation = _um980->getLongitudeDeviation(); + + // The binary message may contain all 0xFFs leading to a very large negative number. + if (longitudeDeviation < -0.01) + longitudeDeviation = 50.0; + if (latitudeDeviation < -0.01) + latitudeDeviation = 50.0; + + // Return the lower of the two Lat/Long deviations + if (longitudeDeviation < latitudeDeviation) + return (longitudeDeviation); + return (latitudeDeviation); + } + return 0; +} + +//---------------------------------------- +// Returns the hours of 24 hour clock or zero if not online +//---------------------------------------- +uint8_t GNSS_UM980::getHour() +{ + if (online.gnss) + return (_um980->getHour()); + return 0; +} + +//---------------------------------------- +const char * GNSS_UM980::getId() +{ + if (online.gnss) + return (_um980->getID()); + return ((char *)"\0"); +} + +//---------------------------------------- +// Get the latitude value +// Returns the latitude value or zero if not online +//---------------------------------------- +double GNSS_UM980::getLatitude() +{ + if (online.gnss) + return (_um980->getLatitude()); + return 0; +} + +//---------------------------------------- +// Query GNSS for current leap seconds +//---------------------------------------- +uint8_t GNSS_UM980::getLeapSeconds() +{ + // TODO Need to find leap seconds in UM980 + return (18); // Default to 18 +} + +//---------------------------------------- +// Get the longitude value +// Outputs: +// Returns the longitude value or zero if not online +//---------------------------------------- +double GNSS_UM980::getLongitude() +{ + if (online.gnss) + return (_um980->getLongitude()); + return 0; +} + +//---------------------------------------- +// Returns two digits of milliseconds or zero if not online +//---------------------------------------- +uint8_t GNSS_UM980::getMillisecond() +{ + if (online.gnss) + return (_um980->getMillisecond()); + return 0; +} + +//---------------------------------------- +// Returns minutes or zero if not online +//---------------------------------------- +uint8_t GNSS_UM980::getMinute() +{ + if (online.gnss) + return (_um980->getMinute()); + return 0; +} + +//---------------------------------------- +// Returns month number or zero if not online +//---------------------------------------- +uint8_t GNSS_UM980::getMonth() +{ + if (online.gnss) + return (_um980->getMonth()); + return 0; +} + +//---------------------------------------- +// Returns nanoseconds or zero if not online +//---------------------------------------- +uint32_t GNSS_UM980::getNanosecond() +{ + if (online.gnss) + // UM980 does not have nanosecond, but it does have millisecond + return (getMillisecond() * 1000L); // Convert to ns + return 0; +} + +//---------------------------------------- +// Given the name of an NMEA message, return the array number +//---------------------------------------- +uint8_t GNSS_UM980::getNmeaMessageNumberByName(const char *msgName) +{ + for (int x = 0; x < MAX_UM980_NMEA_MSG; x++) + { + if (strcmp(umMessagesNMEA[x].msgTextName, msgName) == 0) + return (x); + } + + systemPrintf("getNmeaMessageNumberByName: %s not found\r\n", msgName); + return (0); +} + +//---------------------------------------- +uint32_t GNSS_UM980::getRadioBaudRate() +{ + return (0); // UM980 has no multiplexer +} + +//---------------------------------------- +// Returns the seconds between measurements +//---------------------------------------- +double GNSS_UM980::getRateS() +{ + return (((double)settings.measurementRateMs) / 1000.0); +} + +//---------------------------------------- +const char * GNSS_UM980::getRtcmDefaultString() +{ + return "1005/1074/1084/1094/1124 1Hz & 1033 0.1Hz"; +} + +//---------------------------------------- +const char * GNSS_UM980::getRtcmLowDataRateString() +{ + return "1074/1084/1094/1124 0.5Hz & 1005/1033 0.1Hz"; +} + +//---------------------------------------- +// Given the name of an RTCM message, return the array number +//---------------------------------------- +uint8_t GNSS_UM980::getRtcmMessageNumberByName(const char *msgName) +{ + for (int x = 0; x < MAX_UM980_RTCM_MSG; x++) + { + if (strcmp(umMessagesRTCM[x].msgTextName, msgName) == 0) + return (x); + } + + systemPrintf("getRtcmMessageNumberByName: %s not found\r\n", msgName); + return (0); +} + +//---------------------------------------- +// Returns the number of satellites in view or zero if offline +//---------------------------------------- +uint8_t GNSS_UM980::getSatellitesInView() +{ + if (online.gnss) + return (_um980->getSIV()); + return 0; +} + +//---------------------------------------- +// Returns seconds or zero if not online +//---------------------------------------- +uint8_t GNSS_UM980::getSecond() +{ + if (online.gnss) + return (_um980->getSecond()); + return 0; +} + +//---------------------------------------- +// Get the survey-in mean accuracy +//---------------------------------------- +float GNSS_UM980::getSurveyInMeanAccuracy() +{ + // Not supported on the UM980 + // Return the current HPA instead + return getHorizontalAccuracy(); +} + +//---------------------------------------- +// Return the number of seconds the survey-in process has been running +//---------------------------------------- +int GNSS_UM980::getSurveyInObservationTime() +{ + int elapsedSeconds = (millis() - _autoBaseStartTimer) / 1000; + return (elapsedSeconds); +} + +//---------------------------------------- +// Returns timing accuracy or zero if not online +//---------------------------------------- +uint32_t GNSS_UM980::getTimeAccuracy() +{ + if (online.gnss) + { + // Standard deviation of the receiver clock offset, s. + // UM980 returns seconds, ZED returns nanoseconds. We convert here to ns. + // Return just ns in uint32_t form + double timeDeviation_s = _um980->getTimeOffsetDeviation(); + if (timeDeviation_s > 1.0) + return (999999999); + + uint32_t timeDeviation_ns = timeDeviation_s * 1000000000L; // Convert to nanoseconds + return (timeDeviation_ns); + } + return 0; +} + +//---------------------------------------- +// Returns full year, ie 2023, not 23. +//---------------------------------------- +uint16_t GNSS_UM980::getYear() +{ + if (online.gnss) + return (_um980->getYear()); + return 0; +} + +//---------------------------------------- +// Returns true if the device is in Rover mode +// Currently the only two modes are Rover or Base +//---------------------------------------- +bool GNSS_UM980::inRoverMode() +{ + // Determine which state we are in + if (settings.lastState == STATE_BASE_NOT_STARTED) + return (false); + + return (true); // Default to Rover +} + +//---------------------------------------- +bool GNSS_UM980::isBlocking() +{ + if (online.gnss) + return _um980->isBlocking(); + return false; +} + +//---------------------------------------- +// Date is confirmed once we have GNSS fix +//---------------------------------------- +bool GNSS_UM980::isConfirmedDate() +{ + // UM980 doesn't have this feature. Check for valid date. + return isValidDate(); +} + +//---------------------------------------- +// Time is confirmed once we have GNSS fix +//---------------------------------------- +bool GNSS_UM980::isConfirmedTime() +{ + // UM980 doesn't have this feature. Check for valid time. + return isValidTime(); +} + +//---------------------------------------- +// Return true if GNSS receiver has a higher quality DGPS fix than 3D +//---------------------------------------- +bool GNSS_UM980::isDgpsFixed() +{ + if (online.gnss) + // 17 = Pseudorange differential solution + return (_um980->getPositionType() == 17); + return false; +} + +//---------------------------------------- +// Some functions (L-Band area frequency determination) merely need to know if we have a valid fix, not what type of fix +// This function checks to see if the given platform has reached sufficient fix type to be considered valid +//---------------------------------------- +bool GNSS_UM980::isFixed() +{ + if (online.gnss) + // 16 = 3D Fix (Single) + return (_um980->getPositionType() >= 16); + return false; +} + +//---------------------------------------- +// Used in tpISR() for time pulse synchronization +//---------------------------------------- +bool GNSS_UM980::isFullyResolved() +{ + if (online.gnss) + // UM980 does not have this feature directly. + // getSolutionStatus: + // 0 = Solution computed + // 1 = Insufficient observation + // 3 = No convergence + // 4 = Covariance trace + return (_um980->getSolutionStatus() == 0); + return false; +} + +//---------------------------------------- +// Return true if the GPGGA message is active +//---------------------------------------- +bool GNSS_UM980::isGgaActive() +{ + if (settings.um980MessageRatesNMEA[getNmeaMessageNumberByName("GPGGA")] > 0) + return (true); + return (false); +} + +//---------------------------------------- +bool GNSS_UM980::isPppConverged() +{ + if (online.gnss) + // 69 = Precision Point Positioning + return (_um980->getPositionType() == 69); + return (false); +} + +//---------------------------------------- +bool GNSS_UM980::isPppConverging() +{ + if (online.gnss) + // 68 = PPP solution converging + return (_um980->getPositionType() == 68); + return (false); +} + +//---------------------------------------- +// Some functions (L-Band area frequency determination) merely need to +// know if we have an RTK Fix. This function checks to see if the given +// platform has reached sufficient fix type to be considered valid +//---------------------------------------- +bool GNSS_UM980::isRTKFix() +{ + if (online.gnss) + // 50 = RTK Fixed (Narrow-lane fixed solution) + return (_um980->getPositionType() == 50); + return (false); +} + +//---------------------------------------- +// Some functions (L-Band area frequency determination) merely need to +// know if we have an RTK Float. This function checks to see if the +// given platform has reached sufficient fix type to be considered valid +//---------------------------------------- +bool GNSS_UM980::isRTKFloat() +{ + if (online.gnss) + // 34 = Narrow-land float solution + // 49 = Wide-lane fixed solution + return ((_um980->getPositionType() == 49) || (_um980->getPositionType() == 34)); + return (false); +} + +//---------------------------------------- +// Determine if the survey-in operation is complete +//---------------------------------------- +bool GNSS_UM980::isSurveyInComplete() +{ + return (false); +} + +//---------------------------------------- +// Date will be valid if the RTC is reporting (regardless of GNSS fix) +//---------------------------------------- +bool GNSS_UM980::isValidDate() +{ + if (online.gnss) + // 0 = Invalid + // 1 = valid + // 2 = leap second warning + return (_um980->getDateStatus() == 1); + return (false); +} + +//---------------------------------------- +// Time will be valid if the RTC is reporting (regardless of GNSS fix) +//---------------------------------------- +bool GNSS_UM980::isValidTime() +{ + if (online.gnss) + // 0 = valid + // 3 = invalid + return (_um980->getTimeStatus() == 0); + return (false); +} + +//---------------------------------------- +// Controls the constellations that are used to generate a fix and logged +//---------------------------------------- +void GNSS_UM980::menuConstellations() +{ + while (1) + { + systemPrintln(); + systemPrintln("Menu: Constellations"); + + for (int x = 0; x < MAX_UM980_CONSTELLATIONS; x++) + { + systemPrintf("%d) Constellation %s: ", x + 1, um980ConstellationCommands[x].textName); + if (settings.um980Constellations[x] > 0) + systemPrint("Enabled"); + else + systemPrint("Disabled"); + systemPrintln(); + } + + if (present.galileoHasCapable) + { + systemPrintf("%d) Galileo E6 Corrections: %s\r\n", MAX_UM980_CONSTELLATIONS + 1, + settings.enableGalileoHas ? "Enabled" : "Disabled"); + } + + systemPrintln("x) Exit"); + + int incoming = getUserInputNumber(); // Returns EXIT, TIMEOUT, or long + + if (incoming >= 1 && incoming <= MAX_UM980_CONSTELLATIONS) + { + incoming--; // Align choice to constellation array of 0 to 5 + + settings.um980Constellations[incoming] ^= 1; + } + else if ((incoming == MAX_UM980_CONSTELLATIONS + 1) && present.galileoHasCapable) + { + settings.enableGalileoHas ^= 1; + } + else if (incoming == INPUT_RESPONSE_GETNUMBER_EXIT) + break; + else if (incoming == INPUT_RESPONSE_GETNUMBER_TIMEOUT) + break; + else + printUnknown(incoming); + } + + // Apply current settings to module + gnss->setConstellations(); + + clearBuffer(); // Empty buffer of any newline chars +} + +//---------------------------------------- +void GNSS_UM980::menuMessageBaseRtcm() +{ + menuMessagesSubtype(settings.um980MessageRatesRTCMBase, "RTCMBase"); +} + +//---------------------------------------- +// Control the messages that get broadcast over Bluetooth and logged (if enabled) +//---------------------------------------- +void GNSS_UM980::menuMessages() +{ + while (1) + { + systemPrintln(); + systemPrintln("Menu: GNSS Messages"); + + systemPrintf("Active messages: %d\r\n", gnss->getActiveMessageCount()); + + systemPrintln("1) Set NMEA Messages"); + systemPrintln("2) Set Rover RTCM Messages"); + systemPrintln("3) Set Base RTCM Messages"); + + systemPrintln("10) Reset to Defaults"); + + systemPrintln("x) Exit"); + + int incoming = getUserInputNumber(); // Returns EXIT, TIMEOUT, or long + + if (incoming == 1) + menuMessagesSubtype(settings.um980MessageRatesNMEA, "NMEA"); + else if (incoming == 2) + menuMessagesSubtype(settings.um980MessageRatesRTCMRover, "RTCMRover"); + else if (incoming == 3) + menuMessagesSubtype(settings.um980MessageRatesRTCMBase, "RTCMBase"); + else if (incoming == 10) + { + // Reset rates to defaults + for (int x = 0; x < MAX_UM980_NMEA_MSG; x++) + settings.um980MessageRatesNMEA[x] = umMessagesNMEA[x].msgDefaultRate; + + // For rovers, RTCM should be zero by default. + for (int x = 0; x < MAX_UM980_RTCM_MSG; x++) + settings.um980MessageRatesRTCMRover[x] = 0; + + // Reset RTCM rates to defaults + for (int x = 0; x < MAX_UM980_RTCM_MSG; x++) + settings.um980MessageRatesRTCMBase[x] = umMessagesRTCM[x].msgDefaultRate; + + systemPrintln("Reset to Defaults"); + } + + else if (incoming == INPUT_RESPONSE_GETNUMBER_EXIT) + break; + else if (incoming == INPUT_RESPONSE_GETNUMBER_TIMEOUT) + break; + else + printUnknown(incoming); + } + + clearBuffer(); // Empty buffer of any newline chars + + // Apply these changes at menu exit + if (inRoverMode()) + restartRover = true; + else + restartBase = true; +} + +//---------------------------------------- +// Given a sub type (ie "RTCM", "NMEA") present menu showing messages with this subtype +// Controls the messages that get broadcast over Bluetooth and logged (if enabled) +//---------------------------------------- +void GNSS_UM980::menuMessagesSubtype(float *localMessageRate, const char *messageType) +{ + while (1) + { + systemPrintln(); + systemPrintf("Menu: Message %s\r\n", messageType); + + int endOfBlock = 0; + + if (strcmp(messageType, "NMEA") == 0) + { + endOfBlock = MAX_UM980_NMEA_MSG; + + for (int x = 0; x < MAX_UM980_NMEA_MSG; x++) + systemPrintf("%d) Message %s: %g\r\n", x + 1, umMessagesNMEA[x].msgTextName, + settings.um980MessageRatesNMEA[x]); + } + else if (strcmp(messageType, "RTCMRover") == 0) + { + endOfBlock = MAX_UM980_RTCM_MSG; + + for (int x = 0; x < MAX_UM980_RTCM_MSG; x++) + systemPrintf("%d) Message %s: %g\r\n", x + 1, umMessagesRTCM[x].msgTextName, + settings.um980MessageRatesRTCMRover[x]); + } + else if (strcmp(messageType, "RTCMBase") == 0) + { + endOfBlock = MAX_UM980_RTCM_MSG; + + for (int x = 0; x < MAX_UM980_RTCM_MSG; x++) + systemPrintf("%d) Message %s: %g\r\n", x + 1, umMessagesRTCM[x].msgTextName, + settings.um980MessageRatesRTCMBase[x]); + } + + systemPrintln("x) Exit"); + + int incoming = getUserInputNumber(); // Returns EXIT, TIMEOUT, or long + + if (incoming >= 1 && incoming <= endOfBlock) + { + // Adjust incoming to match array start of 0 + incoming--; + + // Create user prompt + char messageString[100] = ""; + if (strcmp(messageType, "NMEA") == 0) + { + sprintf(messageString, "Enter number of seconds between %s messages (0 to disable)", + umMessagesNMEA[incoming].msgTextName); + } + else if ((strcmp(messageType, "RTCMRover") == 0) || (strcmp(messageType, "RTCMBase") == 0)) + { + sprintf(messageString, "Enter number of seconds between %s messages (0 to disable)", + umMessagesRTCM[incoming].msgTextName); + } + + double newSetting = 0.0; + + // Message rates are 0.05s to 65s + if (getNewSetting(messageString, 0, 65.0, &newSetting) == INPUT_RESPONSE_VALID) + { + // Allowed values: + // 1, 0.5, 0.2, 0.1, 0.05 corresponds to 1Hz, 2Hz, 5Hz, 10Hz, 20Hz respectively. + // 1, 2, 5 corresponds to 1Hz, 0.5Hz, 0.2Hz respectively. + if (newSetting == 0.0) + { + // Allow it + } + else if (newSetting < 1.0) + { + // Deal with 0.0001 to 1.0 + if (newSetting <= 0.05) + newSetting = 0.05; // 20Hz + else if (newSetting <= 0.1) + newSetting = 0.1; // 10Hz + else if (newSetting <= 0.2) + newSetting = 0.2; // 5Hz + else if (newSetting <= 0.5) + newSetting = 0.5; // 2Hz + else + newSetting = 1.0; // 1Hz + } + // 2.7 is not allowed. Change to 2.0. + else if (newSetting >= 1.0) + newSetting = floor(newSetting); + + if (strcmp(messageType, "NMEA") == 0) + settings.um980MessageRatesNMEA[incoming] = (float)newSetting; + if (strcmp(messageType, "RTCMRover") == 0) + settings.um980MessageRatesRTCMRover[incoming] = (float)newSetting; + if (strcmp(messageType, "RTCMBase") == 0) + settings.um980MessageRatesRTCMBase[incoming] = (float)newSetting; + } + } + else if (incoming == INPUT_RESPONSE_GETNUMBER_EXIT) + break; + else if (incoming == INPUT_RESPONSE_GETNUMBER_TIMEOUT) + break; + else + printUnknown(incoming); + } + + settings.updateGNSSSettings = true; // Update the GNSS config at the next boot + + clearBuffer(); // Empty buffer of any newline chars +} + +//---------------------------------------- +// Print the module type and firmware version +//---------------------------------------- +void GNSS_UM980::printModuleInfo() +{ + if (online.gnss) + { + uint8_t modelType = _um980->getModelType(); + + if (modelType == 18) + systemPrint("UM980"); + else + systemPrintf("Unicore Model Unknown %d", modelType); + + systemPrintf(" firmware: %s\r\n", _um980->getVersion()); + } +} + +//---------------------------------------- +// Send data directly from ESP GNSS UART1 to UM980 UART3 +// Returns the number of correction data bytes written +//---------------------------------------- +int GNSS_UM980::pushRawData(uint8_t *dataToSend, int dataLength) +{ + if (online.gnss) + return (serialGNSS->write(dataToSend, dataLength)); + return (0); +} + +//---------------------------------------- +uint16_t GNSS_UM980::rtcmBufferAvailable() +{ + // TODO return(um980RtcmBufferAvailable()); + return (0); +} + +//---------------------------------------- +// If LBand is being used, ignore any RTCM that may come in from the GNSS +//---------------------------------------- +void GNSS_UM980::rtcmOnGnssDisable() +{ + // UM980 does not have a separate interface for RTCM +} + +//---------------------------------------- +// If L-Band is available, but encrypted, allow RTCM through other sources (radio, ESP-Now) to GNSS receiver +//---------------------------------------- +void GNSS_UM980::rtcmOnGnssEnable() +{ + // UM980 does not have a separate interface for RTCM +} + +//---------------------------------------- +uint16_t GNSS_UM980::rtcmRead(uint8_t *rtcmBuffer, int rtcmBytesToRead) +{ + // TODO return(um980RtcmRead(rtcmBuffer, rtcmBytesToRead)); + return (0); +} + +//---------------------------------------- +// Save the current configuration +// Returns true when the configuration was saved and false upon failure +//---------------------------------------- +bool GNSS_UM980::saveConfiguration() +{ + if (online.gnss) + return (_um980->saveConfiguration()); + return false; +} + +//---------------------------------------- +// Set the baud rate on the GNSS port that interfaces between the ESP32 and the GNSS +// This just sets the GNSS side +// Used during Bluetooth testing +//---------------------------------------- +bool GNSS_UM980::setBaudrate(uint32_t baudRate) +{ + if (online.gnss) + // Set the baud rate on COM3 of the UM980 + return setBaudRateCOM3(baudRate); + return false; +} + +//---------------------------------------- +// Set the baud rate on the GNSS port that interfaces between the ESP32 and the GNSS +//---------------------------------------- +bool GNSS_UM980::setBaudRateCOM3(uint32_t baudRate) +{ + if (online.gnss) + return _um980->setPortBaudrate("COM3", baudRate); + return false; +} + +//---------------------------------------- +// Enable all the valid constellations and bands for this platform +// Band support varies between platforms and firmware versions +// We open/close a complete set 19 messages +//---------------------------------------- +bool GNSS_UM980::setConstellations() +{ + bool response = true; + + for (int constellationNumber = 0; constellationNumber < MAX_UM980_CONSTELLATIONS; constellationNumber++) + { + if (settings.um980Constellations[constellationNumber]) + { + if (_um980->enableConstellation(um980ConstellationCommands[constellationNumber].textCommand) == false) + { + if (settings.debugGnss) + systemPrintf("Enable constellation failed at constellationNumber %d %s.", constellationNumber, + um980ConstellationCommands[constellationNumber].textName); + response &= false; // If any one of the commands fails, report failure overall + } + } + else + { + if (_um980->disableConstellation(um980ConstellationCommands[constellationNumber].textCommand) == false) + { + if (settings.debugGnss) + systemPrintf("Disable constellation failed at constellationNumber %d %s.", constellationNumber, + um980ConstellationCommands[constellationNumber].textName); + response &= false; // If any one of the commands fails, report failure overall + } + } + } + + return (response); +} + +//---------------------------------------- +bool GNSS_UM980::setDataBaudRate(uint32_t baud) +{ + return false; // UM980 has no multiplexer +} + +//---------------------------------------- +// Set the elevation in degrees +//---------------------------------------- +bool GNSS_UM980::setElevation(uint8_t elevationDegrees) +{ + if (online.gnss) + return _um980->setElevationAngle(elevationDegrees); + return false; +} + +//---------------------------------------- +bool GNSS_UM980::setHighAccuracyService(bool enableGalileoHas) +{ + bool result = true; + + // Enable E6 and PPP if enabled and possible + if (settings.enableGalileoHas) + { + // E6 reception requires version 11833 or greater + int um980Version = String(_um980->getVersion()).toInt(); // Convert the string response to a value + if (um980Version >= 11833) + { + if (_um980->isConfigurationPresent("CONFIG PPP ENABLE E6-HAS") == false) + { + if (_um980->sendCommand("CONFIG PPP ENABLE E6-HAS")) + systemPrintln("Galileo E6 service enabled"); + else + { + systemPrintln("Galileo E6 service failed to enable"); + result = false; + } + + if (_um980->sendCommand("CONFIG PPP DATUM WGS84")) + systemPrintln("WGS84 Datum applied"); + else + { + systemPrintln("WGS84 Datum failed to apply"); + result = false; + } + } + } + else + { + systemPrintf( + "Current UM980 firmware: v%d. Galileo E6 reception requires v11833 or newer. Please update the " + "firmware on your UM980 to allow for HAS operation. Please see https://bit.ly/sfe-rtk-um980-update\r\n", + um980Version); + // Don't fail the result. Module is still configured, just without HAS. + } + } + else + { + // Turn off HAS/E6 + if (_um980->isConfigurationPresent("CONFIG PPP ENABLE E6-HAS")) + { + if (_um980->sendCommand("CONFIG PPP DISABLE")) + systemPrintln("Galileo E6 service disabled"); + else + { + systemPrintln("Galileo E6 service failed to disable"); + result = false; + } + } + } + return (result); +} + +//---------------------------------------- +// Enable all the valid messages for this platform +// There are many messages so split into batches. VALSET is limited to 64 max per batch +// Uses dummy newCfg and sendCfg values to be sure we open/close a complete set +//---------------------------------------- +bool GNSS_UM980::setMessages(int maxRetries) +{ + // We probably don't need this for the UM980 + // TODO return(um980SetMessages(maxRetries)); + return (true); +} + +//---------------------------------------- +// Enable all the valid messages for this platform over the USB port +// Add 2 to every UART1 key. This is brittle and non-perfect, but works. +//---------------------------------------- +bool GNSS_UM980::setMessagesUsb(int maxRetries) +{ + // We probably don't need this for the UM980 + // TODO return(um980SetMessagesUsb(maxRetries)); + return (true); +} + +//---------------------------------------- +// Set the minimum satellite signal level for navigation. +//---------------------------------------- +bool GNSS_UM980::setMinCnoRadio (uint8_t cnoValue) +{ + if (online.gnss) + { + _um980->setMinCNO(cnoValue); + return true; + } + return false; +} + +//---------------------------------------- +// Set the dynamic model to use for RTK +//---------------------------------------- +bool GNSS_UM980::setModel(uint8_t modelNumber) +{ + if (online.gnss) + { + if (modelNumber == UM980_DYN_MODEL_SURVEY) + return (_um980->setModeRoverSurvey()); + else if (modelNumber == UM980_DYN_MODEL_UAV) + return (_um980->setModeRoverUAV()); + else if (modelNumber == UM980_DYN_MODEL_AUTOMOTIVE) + return (_um980->setModeRoverAutomotive()); + } + return (false); +} + +//---------------------------------------- +bool GNSS_UM980::setMultipathMitigation(bool enableMultipathMitigation) +{ + bool result = true; + + // Enable MMP as required + if (enableMultipathMitigation) + { + if (_um980->isConfigurationPresent("CONFIG MMP ENABLE") == false) + { + if (_um980->sendCommand("CONFIG MMP ENABLE")) + systemPrintln("Multipath Mitigation enabled"); + else + { + systemPrintln("Multipath Mitigation failed to enable"); + result = false; + } + } + } + else + { + // Turn off MMP + if (_um980->isConfigurationPresent("CONFIG MMP ENABLE")) + { + if (_um980->sendCommand("CONFIG MMP DISABLE")) + systemPrintln("Multipath Mitigation disabled"); + else + { + systemPrintln("Multipath Mitigation failed to disable"); + result = false; + } + } + } + return (result); +} + +//---------------------------------------- +bool GNSS_UM980::setRadioBaudRate(uint32_t baud) +{ + return false; // UM980 has no multiplexer +} + +//---------------------------------------- +// Given the number of seconds between desired solution reports, determine measurementRateMs and navigationRate +// measurementRateS > 25 & <= 65535 +// navigationRate >= 1 && <= 127 +// We give preference to limiting a measurementRate to 30 or below due to reported problems with measRates above 30. +//---------------------------------------- +bool GNSS_UM980::setRate(double secondsBetweenSolutions) +{ + // The UM980 does not have a rate setting. Instead the report rate of + // the GNSS messages can be set. For example, 0.5 is 2Hz, 0.2 is 5Hz. + // We assume, if the user wants to set the 'rate' to 5Hz, they want all + // messages set to that rate. + // All NMEA/RTCM for a rover will be based on the measurementRateMs setting + // ie, if a message != 0, then it will be output at the measurementRate. + // All RTCM for a base will be based on a measurementRateMs of 1000 with messages + // that can be reported more slowly than that (ie 1 per 10 seconds). + bool response = true; + + disableAllOutput(); + + // Overwrite any enabled messages with this rate + for (int messageNumber = 0; messageNumber < MAX_UM980_NMEA_MSG; messageNumber++) + { + if (settings.um980MessageRatesNMEA[messageNumber] > 0) + { + settings.um980MessageRatesNMEA[messageNumber] = secondsBetweenSolutions; + } + } + response &= enableNMEA(); // Enact these rates + + // TODO We don't know what state we are in, so we don't + // know which RTCM settings to update. Assume we are + // in rover for now + for (int messageNumber = 0; messageNumber < MAX_UM980_RTCM_MSG; messageNumber++) + { + if (settings.um980MessageRatesRTCMRover[messageNumber] > 0) + { + settings.um980MessageRatesRTCMRover[messageNumber] = secondsBetweenSolutions; + } + } + response &= enableRTCMRover(); // Enact these rates + + // If we successfully set rates, only then record to settings + if (response) + { + uint16_t msBetweenSolutions = secondsBetweenSolutions * 1000; + settings.measurementRateMs = msBetweenSolutions; + } + else + { + systemPrintln("Failed to set measurement and navigation rates"); + return (false); + } + + return (true); +} + +//---------------------------------------- +bool GNSS_UM980::setTalkerGNGGA() +{ + // TODO um980SetTalkerGNGGA(); + return false; +} + +//---------------------------------------- +// Hotstart GNSS to try to get RTK lock +//---------------------------------------- +bool GNSS_UM980::softwareReset() +{ + return false; +} + +//---------------------------------------- +bool GNSS_UM980::standby() +{ + return true; +} + +//---------------------------------------- +// Slightly modified method for restarting survey-in from: +// https://portal.u-blox.com/s/question/0D52p00009IsVoMCAV/restarting-surveyin-on-an-f9p +//---------------------------------------- +bool GNSS_UM980::surveyInReset() +{ + if (online.gnss) + return (_um980->setModeRoverSurvey()); + return false; +} + +//---------------------------------------- +// Start the survey-in operation +// The ZED-F9P is slightly different than the NEO-M8P. See the Integration manual 3.5.8 for more info. +//---------------------------------------- +bool GNSS_UM980::surveyInStart() +{ + if (online.gnss) + { + bool response = true; + + // Start a Self-optimizing Base Station + // We do not use the distance parameter (settings.observationPositionAccuracy) because that + // setting on the UM980 is related to automatically restarting base mode + // at power on (very different from ZED-F9P). + response &= + _um980->setModeBaseAverage(settings.observationSeconds); // Average for a number of seconds (default is 60) + + _autoBaseStartTimer = millis(); // Stamp when averaging began + + if (response == false) + { + systemPrintln("Survey start failed"); + return (false); + } + + return (response); + } + return false; +} + +//---------------------------------------- +// If we have received serial data from the UM980 outside of the Unicore library (ie, from processUart1Message task) +// we can pass data back into the Unicore library to allow it to update its own variables +//---------------------------------------- +void um980UnicoreHandler(uint8_t *buffer, int length) +{ + GNSS_UM980 * um980 = (GNSS_UM980 *)gnss; + um980->unicoreHandler(buffer, length); +} + +//---------------------------------------- +// If we have received serial data from the UM980 outside of the Unicore library (ie, from processUart1Message task) +// we can pass data back into the Unicore library to allow it to update its own variables +//---------------------------------------- +void GNSS_UM980::unicoreHandler(uint8_t *buffer, int length) +{ + _um980->unicoreHandler(buffer, length); +} + +//---------------------------------------- +// Poll routine to update the GNSS state +//---------------------------------------- +void GNSS_UM980::update() +{ + // We don't check serial data here; the gnssReadTask takes care of serial consumption +} + +#endif // COMPILE_UM980 + +//---------------------------------------- +void um980Boot() +{ + digitalWrite(pin_GNSS_DR_Reset, HIGH); // Tell UM980 and DR to boot +} + +//---------------------------------------- +// Force UART connection to UM980 for firmware update on the next boot by creating updateUm980Firmware.txt in +// LittleFS +//---------------------------------------- +bool createUm980Passthrough() +{ + if (online.fs == false) + return false; + + if (LittleFS.exists("/updateUm980Firmware.txt")) + { + if (settings.debugGnss) + systemPrintln("LittleFS updateUm980Firmware.txt already exists"); + return true; + } + + File updateUm980Firmware = LittleFS.open("/updateUm980Firmware.txt", FILE_WRITE); + updateUm980Firmware.close(); + + if (LittleFS.exists("/updateUm980Firmware.txt")) + return true; + + if (settings.debugGnss) + systemPrintln("Unable to create updateUm980Firmware.txt on LittleFS"); + return false; +} + +//---------------------------------------- +void um980FirmwareBeginUpdate() +{ + // Note: We cannot increase the bootloading speed beyond 115200 because + // we would need to alter the UM980 baud, then save to NVM, then allow the UM980 to reset. + // This is workable, but the next time the RTK Torch resets, it assumes communication at 115200bps + // This fails and communication is broken. We could program in some logic that attempts comm at 460800 + // then reconfigures the UM980 to 115200bps, then resets, but autobaud detection in the UM980 library is + // not yet supported. + + // Stop all UART tasks + tasksStopGnssUart(); + + systemPrintln(); + systemPrintln("Entering UM980 direct connect for firmware update and configuration. Disconnect this terminal " + "connection. Use " + "UPrecise to update the firmware. Baudrate: 115200bps. Press the power button to return " + "to normal operation."); + systemFlush(); + + // Make sure ESP-UART1 is connected to UM980 + muxSelectUm980(); + + if (serialGNSS == nullptr) + serialGNSS = new HardwareSerial(2); // Use UART2 on the ESP32 for communication with the GNSS module + + serialGNSS->begin(115200, SERIAL_8N1, pin_GnssUart_RX, pin_GnssUart_TX); + + // UPrecise needs to query the device before entering bootload mode + // Wait for UPrecise to send bootloader trigger (character T followed by character @) before resetting UM980 + bool inBootMode = false; + + // Echo everything to/from UM980 + while (1) + { + // Data coming from UM980 to external USB + if (serialGNSS->available()) + Serial.write(serialGNSS->read()); + + // Data coming from external USB to UM980 + if (Serial.available()) + { + byte incoming = Serial.read(); + serialGNSS->write(incoming); + + // Detect bootload sequence + if (inBootMode == false && incoming == 'T') + { + byte nextIncoming = Serial.peek(); + if (nextIncoming == '@') + { + // Reset UM980 + um980Reset(); + delay(25); + um980Boot(); + + inBootMode = true; + } + } + } + + if (digitalRead(pin_powerButton) == HIGH) + { + while (digitalRead(pin_powerButton) == HIGH) + delay(100); + + // Remove file and reset to exit pass-through mode + um980FirmwareRemoveUpdate(); + + // Beep to indicate exit + beepOn(); + delay(300); + beepOff(); + delay(100); + beepOn(); + delay(300); + beepOff(); + + systemPrintln("Exiting UM980 passthrough mode"); + systemFlush(); // Complete prints + + ESP.restart(); + } + } + + systemFlush(); // Complete prints +} + +//---------------------------------------- +// Check if updateUm980Firmware.txt exists +//---------------------------------------- +bool um980FirmwareCheckUpdate() +{ + if (online.fs == false) + return false; + + if (LittleFS.exists("/updateUm980Firmware.txt")) + { + if (settings.debugGnss) + systemPrintln("LittleFS updateUm980Firmware.txt exists"); + + // We do not remove the file here. See removeupdateUm980Firmware(). + + return true; + } + + return false; +} + +//---------------------------------------- +void um980FirmwareRemoveUpdate() +{ + if (online.fs == false) + return; + + if (LittleFS.exists("/updateUm980Firmware.txt")) + { + if (settings.debugGnss) + systemPrintln("Removing updateUm980Firmware.txt "); + + LittleFS.remove("/updateUm980Firmware.txt"); + } +} + +//---------------------------------------- +void um980Reset() +{ + digitalWrite(pin_GNSS_DR_Reset, LOW); // Tell UM980 and DR to reset +} diff --git a/Firmware/RTK_Everywhere/GNSS_ZED.h b/Firmware/RTK_Everywhere/GNSS_ZED.h new file mode 100644 index 000000000..b9da553b2 --- /dev/null +++ b/Firmware/RTK_Everywhere/GNSS_ZED.h @@ -0,0 +1,373 @@ +/*------------------------------------------------------------------------------ +GNSS_ZED.h + + Declarations and definitions for the GNSS_ZED implementation +------------------------------------------------------------------------------*/ + +#ifndef __GNSS_ZED_H__ +#define __GNSS_ZED_H__ + +#include //http://librarymanager/All#SparkFun_u-blox_GNSS_v3 + +class GNSS_ZED : GNSS +{ + private: + + // Use Michael's lock/unlock methods to prevent the GNSS UART task from + // calling checkUblox during a sendCommand and waitForResponse. + // Also prevents pushRawData from being called. + // + // Revert to a simple bool lock. The Mutex was causing occasional panics caused by + // vTaskPriorityDisinheritAfterTimeout in lock() (I think possibly / probably caused by the GNSS not being pinned to + // one core? + bool iAmLocked = false; + + SFE_UBLOX_GNSS_SUPER * _zed = nullptr; // Don't instantiate until we know what gnssPlatform we're on + + public: + + // If we have decryption keys, configure module + // Note: don't check online.lband_neo here. We could be using ip corrections + void applyPointPerfectKeys(); + + // Set RTCM for base mode to defaults (1005/1074/1084/1094/1124 1Hz & 1230 0.1Hz) + void baseRtcmDefault(); + + // Reset to Low Bandwidth Link (1074/1084/1094/1124 0.5Hz & 1005/1230 0.1Hz) + virtual void baseRtcmLowDataRate(); + + // Connect to GNSS and identify particulars + void begin(); + + // Setup TM2 time stamp input as need + // Outputs: + // Returns true when an external event occurs and false if no event + bool beginExternalEvent(); + + // Setup the timepulse output on the PPS pin for external triggering + // Outputs + // Returns true if the pin was successfully setup and false upon + // failure + bool beginPPS(); + + bool checkNMEARates(); + + bool checkPPPRates(); + + // Configure the Base + // Outputs: + // Returns true if successfully configured and false upon failure + bool configureBase(); + + // Configure specific aspects of the receiver for NTP mode + bool configureNtpMode(); + + // Setup the general configuration of the GNSS + // Not Rover or Base specific (ie, baud rates) + // Outputs: + // Returns true if successfully configured and false upon failure + bool configureRadio(); + + // Configure the Rover + // Outputs: + // Returns true if successfully configured and false upon failure + bool configureRover(); + + void debuggingDisable(); + + void debuggingEnable(); + + void enableGgaForNtrip(); + + // Enable RTCM 1230. This is the GLONASS bias sentence and is transmitted + // even if there is no GPS fix. We use it to test serial output. + // Outputs: + // Returns true if successfully started and false upon failure + bool enableRTCMTest(); + + // Restore the GNSS to the factory settings + void factoryReset(); + + uint16_t fileBufferAvailable(); + + uint16_t fileBufferExtractData(uint8_t *fileBuffer, int fileBytesToRead); + + // Start the base using fixed coordinates + // Outputs: + // Returns true if successfully started and false upon failure + bool fixedBaseStart(); + + // Return the number of active/enabled messages + uint8_t getActiveMessageCount(); + + // Get the altitude + // Outputs: + // Returns the altitude in meters or zero if the GNSS is offline + double getAltitude(); + + // Returns the carrier solution or zero if not online + uint8_t getCarrierSolution(); + + virtual uint32_t getDataBaudRate(); + + // Returns the day number or zero if not online + uint8_t getDay(); + + // Return the number of milliseconds since GNSS data was last updated + uint16_t getFixAgeMilliseconds(); + + // Returns the fix type or zero if not online + uint8_t getFixType(); + + // Returns the hours of 24 hour clock or zero if not online + uint8_t getHour(); + + // Get the horizontal position accuracy + // Outputs: + // Returns the horizontal position accuracy or zero if offline + float getHorizontalAccuracy(); + + const char * getId(); + + // Get the latitude value + // Outputs: + // Returns the latitude value or zero if not online + double getLatitude(); + + // Query GNSS for current leap seconds + uint8_t getLeapSeconds(); + + // Get the longitude value + // Outputs: + // Returns the longitude value or zero if not online + double getLongitude(); + + // Given the name of a message, return the array number + uint8_t getMessageNumberByName(const char *msgName); + + // Given the name of a message, find it, and return the rate + uint8_t getMessageRateByName(const char *msgName); + + // Returns two digits of milliseconds or zero if not online + uint8_t getMillisecond(); + + // Returns minutes or zero if not online + uint8_t getMinute(); + + // Returns month number or zero if not online + uint8_t getMonth(); + + // Returns nanoseconds or zero if not online + uint32_t getNanosecond(); + + // Count the number of NAV2 messages with rates more than 0. Used for determining if we need the enable + // the global NAV2 feature. + uint8_t getNAV2MessageCount(); + + virtual uint32_t getRadioBaudRate(); + + // Returns the seconds between solutions + double getRateS(); + + const char * getRtcmDefaultString(); + + const char * getRtcmLowDataRateString(); + + // Returns the number of satellites in view or zero if offline + uint8_t getSatellitesInView(); + + // Returns seconds or zero if not online + uint8_t getSecond(); + + // Get the survey-in mean accuracy + // Outputs: + // Returns the mean accuracy or zero (0) + float getSurveyInMeanAccuracy(); + + // Return the number of seconds the survey-in process has been running + int getSurveyInObservationTime(); + + // Returns timing accuracy or zero if not online + uint32_t getTimeAccuracy(); + + // Returns full year, ie 2023, not 23. + uint16_t getYear(); + + bool isBlocking(); + + // Date is confirmed once we have GNSS fix + bool isConfirmedDate(); + + // Date is confirmed once we have GNSS fix + bool isConfirmedTime(); + + // Return true if GNSS receiver has a higher quality DGPS fix than 3D + bool isDgpsFixed(); + + // Some functions (L-Band area frequency determination) merely need + // to know if we have a valid fix, not what type of fix + // This function checks to see if the given platform has reached + // sufficient fix type to be considered valid + bool isFixed(); + + // Used in tpISR() for time pulse synchronization + bool isFullyResolved(); + + bool isPppConverged(); + + bool isPppConverging(); + + // Some functions (L-Band area frequency determination) merely need + // to know if we have an RTK Fix. This function checks to see if the + // given platform has reached sufficient fix type to be considered valid + bool isRTKFix(); + + // Some functions (L-Band area frequency determination) merely need + // to know if we have an RTK Float. This function checks to see if + // the given platform has reached sufficient fix type to be considered + // valid + bool isRTKFloat(); + + // Determine if the survey-in operation is complete + // Outputs: + // Returns true if the survey-in operation is complete and false + // if the operation is still running + bool isSurveyInComplete(); + + // Date will be valid if the RTC is reporting (regardless of GNSS fix) + bool isValidDate(); + + // Time will be valid if the RTC is reporting (regardless of GNSS fix) + bool isValidTime(); + + // Disable data output from the NEO + bool lBandCommunicationDisable(); + + // Enable data output from the NEO + bool lBandCommunicationEnable(); + + bool lock(void); + + bool lockCreate(void); + + void lockDelete(void); + + // Controls the constellations that are used to generate a fix and logged + void menuConstellations(); + + void menuMessageBaseRtcm(); + + // Control the messages that get broadcast over Bluetooth and logged (if enabled) + void menuMessages(); + + // Print the module type and firmware version + void printModuleInfo(); + + // Send correction data to the GNSS + // Inputs: + // dataToSend: Address of a buffer containing the data + // dataLength: The number of valid data bytes in the buffer + // Outputs: + // Returns the number of correction data bytes written + int pushRawData(uint8_t *dataToSend, int dataLength); + + uint16_t rtcmBufferAvailable(); + + // If L-Band is available, but encrypted, allow RTCM through other sources (radio, ESP-Now) to GNSS receiver + void rtcmOnGnssDisable(); + + // If LBand is being used, ignore any RTCM that may come in from the GNSS + void rtcmOnGnssEnable(); + + uint16_t rtcmRead(uint8_t *rtcmBuffer, int rtcmBytesToRead); + + // Save the current configuration + // Outputs: + // Returns true when the configuration was saved and false upon failure + bool saveConfiguration(); + + // Set the baud rate on the GNSS port that interfaces between the ESP32 and the GNSS + // This just sets the GNSS side + // Used during Bluetooth testing + // Inputs: + // baudRate: The desired baudrate + bool setBaudrate(uint32_t baudRate); + + // Enable all the valid constellations and bands for this platform + bool setConstellations(); + + bool setDataBaudRate(uint32_t baud); + + // Set the elevation in degrees + // Inputs: + // elevationDegrees: The elevation value in degrees + bool setElevation(uint8_t elevationDegrees); + + // Given a unique string, find first and last records containing that string in message array + void setMessageOffsets(const ubxMsg *localMessage, const char *messageType, int &startOfBlock, int &endOfBlock); + + // Given the name of a message, find it, and set the rate + bool setMessageRateByName(const char *msgName, uint8_t msgRate); + + // Enable all the valid messages for this platform + bool setMessages(int maxRetries); + + // Enable all the valid messages for this platform over the USB port + bool setMessagesUsb(int maxRetries); + + // Set the minimum satellite signal level for navigation. + bool setMinCnoRadio (uint8_t cnoValue); + + // Set the dynamic model to use for RTK + // Inputs: + // modelNumber: Number of the model to use, provided by radio library + bool setModel(uint8_t modelNumber); + + bool setRadioBaudRate(uint32_t baud); + + // Specify the interval between solutions + // Inputs: + // secondsBetweenSolutions: Number of seconds between solutions + // Outputs: + // Returns true if the rate was successfully set and false upon + // failure + bool setRate(double secondsBetweenSolutions); + + bool setTalkerGNGGA(); + + // Hotstart GNSS to try to get RTK lock + bool softwareReset(); + + bool standby(); + + // Callback to save the high precision data + // Inputs: + // ubxDataStruct: Address of an UBX_NAV_HPPOSLLH_data_t structure + // containing the high precision position data + void storeHPdataRadio(UBX_NAV_HPPOSLLH_data_t *ubxDataStruct); + + // Callback to save the PVT data + void storePVTdataRadio(UBX_NAV_PVT_data_t *ubxDataStruct); + + // Reset the survey-in operation + // Outputs: + // Returns true if the survey-in operation was reset successfully + // and false upon failure + bool surveyInReset(); + + // Start the survey-in operation + // Outputs: + // Return true if successful and false upon failure + bool surveyInStart(); + + int ubxConstellationIDToIndex(int id); + + void unlock(void); + + // Poll routine to update the GNSS state + void update(); + + void updateCorrectionsSource(uint8_t source); +}; + +#endif // __GNSS_ZED_H__ diff --git a/Firmware/RTK_Everywhere/GNSS_ZED.ino b/Firmware/RTK_Everywhere/GNSS_ZED.ino new file mode 100644 index 000000000..053461ed8 --- /dev/null +++ b/Firmware/RTK_Everywhere/GNSS_ZED.ino @@ -0,0 +1,2525 @@ +/*------------------------------------------------------------------------------ +GNSS_ZED.ino + + Implementation of the GNSS_ZED class +------------------------------------------------------------------------------*/ + +//---------------------------------------- +// If we have decryption keys, configure module +// Note: don't check online.lband_neo here. We could be using ip corrections +//---------------------------------------- +void GNSS_ZED::applyPointPerfectKeys() +{ + if (online.gnss == false) + { + if (settings.debugCorrections) + systemPrintln("ZED-F9P not available"); + return; + } + + // NEO-D9S encrypted PMP messages are only supported on ZED-F9P firmware v1.30 and above + if (gnssFirmwareVersionInt < 130) + { + systemPrintln("Error: PointPerfect corrections currently supported by ZED-F9P firmware v1.30 and above. " + "Please upgrade your ZED firmware: " + "https://learn.sparkfun.com/tutorials/how-to-upgrade-firmware-of-a-u-blox-gnss-receiver"); + return; + } + + if (strlen(settings.pointPerfectNextKey) > 0) + { + const uint8_t currentKeyLengthBytes = 16; + const uint8_t nextKeyLengthBytes = 16; + + uint16_t currentKeyGPSWeek; + uint32_t currentKeyGPSToW; + long long epoch = thingstreamEpochToGPSEpoch(settings.pointPerfectCurrentKeyStart); + epochToWeekToW(epoch, ¤tKeyGPSWeek, ¤tKeyGPSToW); + + uint16_t nextKeyGPSWeek; + uint32_t nextKeyGPSToW; + epoch = thingstreamEpochToGPSEpoch(settings.pointPerfectNextKeyStart); + epochToWeekToW(epoch, &nextKeyGPSWeek, &nextKeyGPSToW); + + // If we are on a L-Band-only or L-Band+IP, set the SOURCE to 1 (L-Band) + // Else set the SOURCE to 0 (IP) + // If we are on L-Band+IP and IP corrections start to arrive, the corrections + // priority code will change SOURCE to match + if (strstr(settings.pointPerfectKeyDistributionTopic, "/Lb") != nullptr) + { + updateCorrectionsSource(1); // Set SOURCE to 1 (L-Band) if needed + } + else + { + updateCorrectionsSource(0); // Set SOURCE to 0 (IP) if needed + } + + _zed->setVal8(UBLOX_CFG_MSGOUT_UBX_RXM_COR_I2C, 1); // Enable UBX-RXM-COR messages on I2C + + _zed->setVal8(UBLOX_CFG_NAVHPG_DGNSSMODE, + 3); // Set the differential mode - ambiguities are fixed whenever possible + + bool response = _zed->setDynamicSPARTNKeys(currentKeyLengthBytes, currentKeyGPSWeek, currentKeyGPSToW, + settings.pointPerfectCurrentKey, nextKeyLengthBytes, + nextKeyGPSWeek, nextKeyGPSToW, settings.pointPerfectNextKey); + + if (response == false) + systemPrintln("setDynamicSPARTNKeys failed"); + else + { + if (settings.debugCorrections) + systemPrintln("PointPerfect keys applied"); + online.lbandCorrections = true; + } + } + else + { + if (settings.debugCorrections) + systemPrintln("No PointPerfect keys available"); + } +} + +//---------------------------------------- +// Set RTCM for base mode to defaults (1005/1074/1084/1094/1124 1Hz & 1230 0.1Hz) +//---------------------------------------- +void GNSS_ZED::baseRtcmDefault() +{ + int firstRTCMRecord = getMessageNumberByName("RTCM_1005"); + settings.ubxMessageRatesBase[getMessageNumberByName("RTCM_1005") - firstRTCMRecord] = 1; // 1105 + settings.ubxMessageRatesBase[getMessageNumberByName("RTCM_1074") - firstRTCMRecord] = 1; // 1074 + settings.ubxMessageRatesBase[getMessageNumberByName("RTCM_1077") - firstRTCMRecord] = 0; // 1077 + settings.ubxMessageRatesBase[getMessageNumberByName("RTCM_1084") - firstRTCMRecord] = 1; // 1084 + settings.ubxMessageRatesBase[getMessageNumberByName("RTCM_1087") - firstRTCMRecord] = 0; // 1087 + + settings.ubxMessageRatesBase[getMessageNumberByName("RTCM_1094") - firstRTCMRecord] = 1; // 1094 + settings.ubxMessageRatesBase[getMessageNumberByName("RTCM_1097") - firstRTCMRecord] = 0; // 1097 + settings.ubxMessageRatesBase[getMessageNumberByName("RTCM_1124") - firstRTCMRecord] = 1; // 1124 + settings.ubxMessageRatesBase[getMessageNumberByName("RTCM_1127") - firstRTCMRecord] = 0; // 1127 + settings.ubxMessageRatesBase[getMessageNumberByName("RTCM_1230") - firstRTCMRecord] = 10; // 1230 + + settings.ubxMessageRatesBase[getMessageNumberByName("RTCM_4072_0") - firstRTCMRecord] = 0; // 4072_0 + settings.ubxMessageRatesBase[getMessageNumberByName("RTCM_4072_1") - firstRTCMRecord] = 0; // 4072_1 +} + +//---------------------------------------- +// Reset to Low Bandwidth Link (1074/1084/1094/1124 0.5Hz & 1005/1230 0.1Hz) +//---------------------------------------- +void GNSS_ZED::baseRtcmLowDataRate() +{ + int firstRTCMRecord = getMessageNumberByName("RTCM_1005"); + settings.ubxMessageRatesBase[getMessageNumberByName("RTCM_1005") - firstRTCMRecord] = 10; // 1105 0.1Hz + settings.ubxMessageRatesBase[getMessageNumberByName("RTCM_1074") - firstRTCMRecord] = 2; // 1074 0.5Hz + settings.ubxMessageRatesBase[getMessageNumberByName("RTCM_1077") - firstRTCMRecord] = 0; // 1077 + settings.ubxMessageRatesBase[getMessageNumberByName("RTCM_1084") - firstRTCMRecord] = 2; // 1084 0.5Hz + settings.ubxMessageRatesBase[getMessageNumberByName("RTCM_1087") - firstRTCMRecord] = 0; // 1087 + + settings.ubxMessageRatesBase[getMessageNumberByName("RTCM_1094") - firstRTCMRecord] = 2; // 1094 0.5Hz + settings.ubxMessageRatesBase[getMessageNumberByName("RTCM_1097") - firstRTCMRecord] = 0; // 1097 + settings.ubxMessageRatesBase[getMessageNumberByName("RTCM_1124") - firstRTCMRecord] = 2; // 1124 0.5Hz + settings.ubxMessageRatesBase[getMessageNumberByName("RTCM_1127") - firstRTCMRecord] = 0; // 1127 + settings.ubxMessageRatesBase[getMessageNumberByName("RTCM_1230") - firstRTCMRecord] = 10; // 1230 0.1Hz + + settings.ubxMessageRatesBase[getMessageNumberByName("RTCM_4072_0") - firstRTCMRecord] = 0; // 4072_0 + settings.ubxMessageRatesBase[getMessageNumberByName("RTCM_4072_1") - firstRTCMRecord] = 0; // 4072_1 +} + +//---------------------------------------- +// Connect to GNSS and identify particulars +//---------------------------------------- +void GNSS_ZED::begin() +{ + // Instantiate the library + if (_zed == nullptr) + _zed = new SFE_UBLOX_GNSS_SUPER(); + + // Note: we don't need to skip this for configureViaEthernet because the ZED is on I2C only - not SPI + + if (_zed->begin(*i2c_0) == false) + { + log_d("GNSS Failed to begin. Trying again."); + + // Try again with power on delay + delay(1000); // Wait for ZED-F9P to power up before it can respond to ACK + if (_zed->begin(*i2c_0) == false) + { + systemPrintln("GNSS ZED offline"); + displayGNSSFail(1000); + return; + } + } + + // Increase transactions to reduce transfer time + _zed->i2cTransactionSize = 128; + + // Auto-send Valset messages before the buffer is completely full + _zed->autoSendCfgValsetAtSpaceRemaining(16); + + // Check the firmware version of the ZED-F9P. Based on Example21_ModuleInfo. + if (_zed->getModuleInfo(1100)) // Try to get the module info + { + // Reconstruct the firmware version + snprintf(gnssFirmwareVersion, sizeof(gnssFirmwareVersion), "%s %d.%02d", _zed->getFirmwareType(), + _zed->getFirmwareVersionHigh(), _zed->getFirmwareVersionLow()); + + gnssFirmwareVersionInt = (_zed->getFirmwareVersionHigh() * 100) + _zed->getFirmwareVersionLow(); + + // Check if this is known firmware + //"1.20" - Mostly for F9R HPS 1.20, but also F9P HPG v1.20 + //"1.21" - F9R HPS v1.21 + //"1.30" - ZED-F9P (HPG) released Dec, 2021. Also ZED-F9R (HPS) released Sept, 2022 + //"1.32" - ZED-F9P released May, 2022 + //"1.50" - ZED-F9P released July, 2024 + + const uint8_t knownFirmwareVersions[] = {100, 112, 113, 120, 121, 130, 132, 150}; + bool knownFirmware = false; + for (uint8_t i = 0; i < (sizeof(knownFirmwareVersions) / sizeof(uint8_t)); i++) + { + if (gnssFirmwareVersionInt == knownFirmwareVersions[i]) + knownFirmware = true; + } + + if (!knownFirmware) + { + systemPrintf("Unknown firmware version: %s\r\n", gnssFirmwareVersion); + gnssFirmwareVersionInt = 99; // 0.99 invalid firmware version + } + + // Determine if we have a ZED-F9P or an ZED-F9R + if (strstr(_zed->getModuleName(), "ZED-F9P") == nullptr) + systemPrintf("Unknown ZED module: %s\r\n", _zed->getModuleName()); + + if (strcmp(_zed->getFirmwareType(), "HPG") == 0) + if ((_zed->getFirmwareVersionHigh() == 1) && (_zed->getFirmwareVersionLow() < 30)) + { + systemPrintln( + "ZED-F9P module is running old firmware which does not support SPARTN. Please upgrade: " + "https://docs.sparkfun.com/SparkFun_RTK_Firmware/firmware_update/#updating-u-blox-firmware"); + displayUpdateZEDF9P(3000); + } + + if (strcmp(_zed->getFirmwareType(), "HPS") == 0) + if ((_zed->getFirmwareVersionHigh() == 1) && (_zed->getFirmwareVersionLow() < 21)) + { + systemPrintln( + "ZED-F9R module is running old firmware which does not support SPARTN. Please upgrade: " + "https://docs.sparkfun.com/SparkFun_RTK_Firmware/firmware_update/#updating-u-blox-firmware"); + displayUpdateZEDF9R(3000); + } + + printModuleInfo(); // Print module type and firmware version + } + + UBX_SEC_UNIQID_data_t chipID; + if (_zed->getUniqueChipId(&chipID)) + { + snprintf(gnssUniqueId, sizeof(gnssUniqueId), "%s", _zed->getUniqueChipIdStr(&chipID)); + } + + systemPrintln("GNSS ZED online"); + + online.gnss = true; +} + +//---------------------------------------- +// Setup the timepulse output on the PPS pin for external triggering +// Setup TM2 time stamp input as need +//---------------------------------------- +bool GNSS_ZED::beginExternalEvent() +{ + if (online.gnss == false) + return (false); + + // If our settings haven't changed, trust ZED's settings + if (settings.updateGNSSSettings == false) + { + log_d("Skipping ZED Trigger configuration"); + return (true); + } + + if (settings.dataPortChannel != MUX_PPS_EVENTTRIGGER) + return (true); // No need to configure PPS if port is not selected + + bool response = true; + + if (settings.enableExternalHardwareEventLogging) + { + _zed->setAutoTIMTM2callbackPtr( + &eventTriggerReceived); // Enable automatic TIM TM2 messages with callback to eventTriggerReceived + } + else + _zed->setAutoTIMTM2callbackPtr(nullptr); + + return (response); +} + +//---------------------------------------- +// Setup the timepulse output on the PPS pin for external triggering +//---------------------------------------- +bool GNSS_ZED::beginPPS() +{ + if (online.gnss == false) + return (false); + + // If our settings haven't changed, trust ZED's settings + if (settings.updateGNSSSettings == false) + { + systemPrintln("Skipping ZED Trigger configuration"); + return (true); + } + + if (settings.dataPortChannel != MUX_PPS_EVENTTRIGGER) + return (true); // No need to configure PPS if port is not selected + + bool response = true; + + response &= _zed->newCfgValset(); + response &= _zed->addCfgValset(UBLOX_CFG_TP_PULSE_DEF, 0); // Time pulse definition is a period (in us) + response &= _zed->addCfgValset(UBLOX_CFG_TP_PULSE_LENGTH_DEF, 1); // Define timepulse by length (not ratio) + response &= + _zed->addCfgValset(UBLOX_CFG_TP_USE_LOCKED_TP1, + 1); // Use CFG-TP-PERIOD_LOCK_TP1 and CFG-TP-LEN_LOCK_TP1 as soon as GNSS time is valid + response &= _zed->addCfgValset(UBLOX_CFG_TP_TP1_ENA, settings.enableExternalPulse); // Enable/disable timepulse + response &= + _zed->addCfgValset(UBLOX_CFG_TP_POL_TP1, settings.externalPulsePolarity); // 0 = falling, 1 = rising edge + + // While the module is _locking_ to GNSS time, turn off pulse + response &= _zed->addCfgValset(UBLOX_CFG_TP_PERIOD_TP1, 1000000); // Set the period between pulses in us + response &= _zed->addCfgValset(UBLOX_CFG_TP_LEN_TP1, 0); // Set the pulse length in us + + // When the module is _locked_ to GNSS time, make it generate 1Hz (Default is 100ms high, 900ms low) + response &= _zed->addCfgValset(UBLOX_CFG_TP_PERIOD_LOCK_TP1, + settings.externalPulseTimeBetweenPulse_us); // Set the period between pulses is us + response &= + _zed->addCfgValset(UBLOX_CFG_TP_LEN_LOCK_TP1, settings.externalPulseLength_us); // Set the pulse length in us + response &= _zed->sendCfgValset(); + + if (response == false) + systemPrintln("beginExternalTriggers config failed"); + + return (response); +} + +//---------------------------------------- +bool GNSS_ZED::checkNMEARates() +{ + if (online.gnss) + return (getMessageRateByName("NMEA_GGA") > 0 && getMessageRateByName("NMEA_GSA") > 0 && + getMessageRateByName("NMEA_GST") > 0 && getMessageRateByName("NMEA_GSV") > 0 && + getMessageRateByName("NMEA_RMC") > 0); + return false; +} + +//---------------------------------------- +bool GNSS_ZED::checkPPPRates() +{ + if (online.gnss) + return (getMessageRateByName("RXM_RAWX") > 0 && getMessageRateByName("RXM_SFRBX") > 0); + return false; +} + +//---------------------------------------- +// Configure specific aspects of the receiver for base mode +//---------------------------------------- +bool GNSS_ZED::configureBase() +{ + if (online.gnss == false) + return (false); + + // If our settings haven't changed, and this is first config since power on, trust ZED's settings + if (settings.updateGNSSSettings == false && firstPowerOn) + { + firstPowerOn = false; // Next time user switches modes, new settings will be applied + log_d("Skipping ZED Base configuration"); + return (true); + } + + firstPowerOn = false; // If we switch between rover/base in the future, force config of module. + + update(); // Regularly poll to get latest data + + _zed->setNMEAGPGGAcallbackPtr( + nullptr); // Disable GPGGA call back that may have been set during Rover NTRIP Client mode + + bool success = false; + int tryNo = -1; + + // Try up to MAX_SET_MESSAGES_RETRIES times to configure the GNSS + // This corrects occasional failures seen on the Reference Station where the GNSS is connected via SPI + // instead of I2C and UART1. I believe the SETVAL ACK is occasionally missed due to the level of messages being + // processed. + while ((++tryNo < MAX_SET_MESSAGES_RETRIES) && !success) + { + bool response = true; + + // In Base mode we force 1Hz + response &= _zed->newCfgValset(); + response &= _zed->addCfgValset(UBLOX_CFG_RATE_MEAS, 1000); + response &= _zed->addCfgValset(UBLOX_CFG_RATE_NAV, 1); + + // Since we are at 1Hz, allow GSV NMEA to be reported at whatever the user has chosen + response &= _zed->addCfgValset(ubxMessages[8].msgConfigKey, + settings.ubxMessageRates[8]); // Update rate on module + + response &= + _zed->addCfgValset(UBLOX_CFG_MSGOUT_NMEA_ID_GGA_I2C, + 0); // Disable NMEA message that may have been set during Rover NTRIP Client mode + + // Survey mode is only available on ZED-F9P modules + if (commandSupported(UBLOX_CFG_TMODE_MODE)) + response &= _zed->addCfgValset(UBLOX_CFG_TMODE_MODE, 0); // Disable survey-in mode + + // Note that using UBX-CFG-TMODE3 to set the receiver mode to Survey In or to Fixed Mode, will set + // automatically the dynamic platform model (CFG-NAVSPG-DYNMODEL) to Stationary. + // response &= _zed->addCfgValset(UBLOX_CFG_NAVSPG_DYNMODEL, (dynModel)settings.dynamicModel); //Not needed + + // For most RTK products, the GNSS is interfaced via both I2C and UART1. Configuration and PVT/HPPOS messages + // are configured over I2C. Any messages that need to be logged are output on UART1, and received by this code + // using serialGNSS-> In base mode the RTK device should output RTCM over all ports: (Primary) UART2 in case the + // RTK device is connected via radio to rover (Optional) I2C in case user wants base to connect to WiFi and + // NTRIP Caster (Seconday) USB in case the RTK device is used as an NTRIP caster connected to SBC or other + // (Tertiary) UART1 in case RTK device is sending RTCM to a phone that is then NTRIP Caster + + // Find first RTCM record in ubxMessage array + int firstRTCMRecord = getMessageNumberByName("RTCM_1005"); + + // ubxMessageRatesBase is an array of ~12 uint8_ts + // ubxMessage is an array of ~80 messages + // We use firstRTCMRecord as an offset for the keys, but use x as the rate + + for (int x = 0; x < MAX_UBX_MSG_RTCM; x++) + { + response &= _zed->addCfgValset(ubxMessages[firstRTCMRecord + x].msgConfigKey - 1, + settings.ubxMessageRatesBase[x]); // UBLOX_CFG UART1 - 1 = I2C + response &= _zed->addCfgValset(ubxMessages[firstRTCMRecord + x].msgConfigKey, + settings.ubxMessageRatesBase[x]); // UBLOX_CFG UART1 + + // Disable messages on SPI + response &= _zed->addCfgValset(ubxMessages[firstRTCMRecord + x].msgConfigKey + 3, + 0); // UBLOX_CFG UART1 + 3 = SPI + } + + // Update message rates for UART2 and USB + for (int x = 0; x < MAX_UBX_MSG_RTCM; x++) + { + response &= _zed->addCfgValset(ubxMessages[firstRTCMRecord + x].msgConfigKey + 1, + settings.ubxMessageRatesBase[x]); // UBLOX_CFG UART1 + 1 = UART2 + response &= _zed->addCfgValset(ubxMessages[firstRTCMRecord + x].msgConfigKey + 2, + settings.ubxMessageRatesBase[x]); // UBLOX_CFG UART1 + 2 = USB + } + + response &= _zed->addCfgValset(UBLOX_CFG_NAVSPG_INFIL_MINELEV, settings.minElev); // Set minimum elevation + + response &= _zed->sendCfgValset(); // Closing value + + if (response) + success = true; + } + + if (!success) + systemPrintln("Base config fail"); + + return (success); +} + +//---------------------------------------- +// Configure specific aspects of the receiver for NTP mode +//---------------------------------------- +bool GNSS_ZED::configureNtpMode() +{ + bool success = false; + + if (online.gnss == false) + return (false); + + gnss->update(); // Regularly poll to get latest data + + // Disable GPGGA call back that may have been set during Rover NTRIP Client mode + _zed->setNMEAGPGGAcallbackPtr(nullptr); + + int tryNo = -1; + + // Try up to MAX_SET_MESSAGES_RETRIES times to configure the GNSS + // This corrects occasional failures seen on the Reference Station where the GNSS is connected via SPI + // instead of I2C and UART1. I believe the SETVAL ACK is occasionally missed due to the level of messages being + // processed. + while ((++tryNo < MAX_SET_MESSAGES_RETRIES) && !success) + { + bool response = true; + + // In NTP mode we force 1Hz + response &= _zed->newCfgValset(); + response &= _zed->addCfgValset(UBLOX_CFG_RATE_MEAS, 1000); + response &= _zed->addCfgValset(UBLOX_CFG_RATE_NAV, 1); + + // Survey mode is only available on ZED-F9P modules + response &= _zed->addCfgValset(UBLOX_CFG_TMODE_MODE, 0); // Disable survey-in mode + + // Set dynamic model to stationary + response &= _zed->addCfgValset(UBLOX_CFG_NAVSPG_DYNMODEL, DYN_MODEL_STATIONARY); // Set dynamic model + + // Set time pulse to 1Hz (100:900) + response &= _zed->addCfgValset(UBLOX_CFG_TP_PULSE_DEF, 0); // Time pulse definition is a period (in us) + response &= _zed->addCfgValset(UBLOX_CFG_TP_PULSE_LENGTH_DEF, 1); // Define timepulse by length (not ratio) + response &= _zed->addCfgValset( + UBLOX_CFG_TP_USE_LOCKED_TP1, + 1); // Use CFG-TP-PERIOD_LOCK_TP1 and CFG-TP-LEN_LOCK_TP1 as soon as GNSS time is valid + response &= _zed->addCfgValset(UBLOX_CFG_TP_TP1_ENA, 1); // Enable timepulse + response &= _zed->addCfgValset(UBLOX_CFG_TP_POL_TP1, 1); // 1 = rising edge + + // While the module is _locking_ to GNSS time, turn off pulse + response &= _zed->addCfgValset(UBLOX_CFG_TP_PERIOD_TP1, 1000000); // Set the period between pulses in us + response &= _zed->addCfgValset(UBLOX_CFG_TP_LEN_TP1, 0); // Set the pulse length in us + + // When the module is _locked_ to GNSS time, make it generate 1Hz (100ms high, 900ms low) + response &= _zed->addCfgValset(UBLOX_CFG_TP_PERIOD_LOCK_TP1, 1000000); // Set the period between pulses is us + response &= _zed->addCfgValset(UBLOX_CFG_TP_LEN_LOCK_TP1, 100000); // Set the pulse length in us + + // Ensure pulse is aligned to top-of-second. This is the default. Set it here just to make sure. + response &= _zed->addCfgValset(UBLOX_CFG_TP_ALIGN_TO_TOW_TP1, 1); + + // Set the time grid to UTC. This is the default. Set it here just to make sure. + response &= _zed->addCfgValset(UBLOX_CFG_TP_TIMEGRID_TP1, 0); // 0=UTC; 1=GPS + + // Sync to GNSS. This is the default. Set it here just to make sure. + response &= _zed->addCfgValset(UBLOX_CFG_TP_SYNC_GNSS_TP1, 1); + + response &= _zed->addCfgValset(UBLOX_CFG_NAVSPG_INFIL_MINELEV, settings.minElev); // Set minimum elevation + + // Ensure PVT, HPPOSLLH and TP messages are being output at 1Hz on the correct port + response &= _zed->addCfgValset(UBLOX_CFG_MSGOUT_UBX_NAV_PVT_I2C, 1); + response &= _zed->addCfgValset(UBLOX_CFG_MSGOUT_UBX_NAV_HPPOSLLH_I2C, 1); + response &= _zed->addCfgValset(UBLOX_CFG_MSGOUT_UBX_TIM_TP_I2C, 1); + + response &= _zed->sendCfgValset(); // Closing value + + if (response) + success = true; + } + + if (!success) + systemPrintln("NTP config fail"); + + return (success); +} + +//---------------------------------------- +// Setup the u-blox module for any setup (base or rover) +// In general we check if the setting is incorrect before writing it. Otherwise, the set commands have, on rare +// occasion, become corrupt. The worst is when the I2C port gets turned off or the I2C address gets borked. +//---------------------------------------- +bool GNSS_ZED::configureRadio() +{ + if (online.gnss == false) + return (false); + + bool response = true; + + // Turn on/off debug messages + if (settings.debugGnss) + _zed->enableDebugging(Serial, true); // Enable only the critical debug messages over Serial + else + _zed->disableDebugging(); + + // Check if the ubxMessageRates or ubxMessageRatesBase need to be defaulted + // Redundant - also done by gnssConfigure + // checkGNSSArrayDefaults(); + + _zed->setAutoPVTcallbackPtr(&storePVTdata); // Enable automatic NAV PVT messages with callback to storePVTdata + _zed->setAutoHPPOSLLHcallbackPtr( + &storeHPdata); // Enable automatic NAV HPPOSLLH messages with callback to storeHPdata + _zed->setRTCM1005InputcallbackPtr( + &storeRTCM1005data); // Configure a callback for RTCM 1005 - parsed from pushRawData + _zed->setRTCM1006InputcallbackPtr( + &storeRTCM1006data); // Configure a callback for RTCM 1006 - parsed from pushRawData + + if (present.timePulseInterrupt) + _zed->setAutoTIMTPcallbackPtr( + &storeTIMTPdata); // Enable automatic TIM TP messages with callback to storeTIMTPdata + + // Configuring the ZED can take more than 2000ms. We save configuration to + // ZED so there is no need to update settings unless user has modified + // the settings file or internal settings. + if (settings.updateGNSSSettings == false) + { + systemPrintln("ZED-F9x configuration maintained"); + return (true); + } + + // Wait for initial report from module + int maxWait = 2000; + startTime = millis(); + while (_pvtUpdated == false) + { + update(); // Regularly poll to get latest data + + delay(10); + if ((millis() - startTime) > maxWait) + { + log_d("PVT Update failed"); + break; + } + } + + // The first thing we do is go to 1Hz to lighten any I2C traffic from a previous configuration + response &= _zed->newCfgValset(); + response &= _zed->addCfgValset(UBLOX_CFG_RATE_MEAS, 1000); + response &= _zed->addCfgValset(UBLOX_CFG_RATE_NAV, 1); + + if (commandSupported(UBLOX_CFG_TMODE_MODE)) + response &= _zed->addCfgValset(UBLOX_CFG_TMODE_MODE, 0); // Disable survey-in mode + + // UART1 will primarily be used to pass NMEA and UBX from ZED to ESP32 (eventually to cell phone) + // but the phone can also provide RTCM data and a user may want to configure the ZED over Bluetooth. + // So let's be sure to enable UBX+NMEA+RTCM on the input + response &= _zed->addCfgValset(UBLOX_CFG_UART1OUTPROT_UBX, 1); + response &= _zed->addCfgValset(UBLOX_CFG_UART1OUTPROT_NMEA, 1); + if (commandSupported(UBLOX_CFG_UART1OUTPROT_RTCM3X)) + response &= _zed->addCfgValset(UBLOX_CFG_UART1OUTPROT_RTCM3X, 1); + response &= _zed->addCfgValset(UBLOX_CFG_UART1INPROT_UBX, 1); + response &= _zed->addCfgValset(UBLOX_CFG_UART1INPROT_NMEA, 1); + response &= _zed->addCfgValset(UBLOX_CFG_UART1INPROT_RTCM3X, 1); + if (commandSupported(UBLOX_CFG_UART1INPROT_SPARTN)) + response &= _zed->addCfgValset(UBLOX_CFG_UART1INPROT_SPARTN, 0); + + response &= _zed->addCfgValset(UBLOX_CFG_UART1_BAUDRATE, + settings.dataPortBaud); // Defaults to 230400 to maximize message output support + response &= _zed->addCfgValset( + UBLOX_CFG_UART2_BAUDRATE, + settings.radioPortBaud); // Defaults to 57600 to match SiK telemetry radio firmware default + + // Disable SPI port - This is just to remove some overhead by ZED + response &= _zed->addCfgValset(UBLOX_CFG_SPIOUTPROT_UBX, 0); + response &= _zed->addCfgValset(UBLOX_CFG_SPIOUTPROT_NMEA, 0); + if (commandSupported(UBLOX_CFG_SPIOUTPROT_RTCM3X)) + response &= _zed->addCfgValset(UBLOX_CFG_SPIOUTPROT_RTCM3X, 0); + + response &= _zed->addCfgValset(UBLOX_CFG_SPIINPROT_UBX, 0); + response &= _zed->addCfgValset(UBLOX_CFG_SPIINPROT_NMEA, 0); + response &= _zed->addCfgValset(UBLOX_CFG_SPIINPROT_RTCM3X, 0); + if (commandSupported(UBLOX_CFG_SPIINPROT_SPARTN)) + response &= _zed->addCfgValset(UBLOX_CFG_SPIINPROT_SPARTN, 0); + + // Set the UART2 to only do RTCM (in case this device goes into base mode) + response &= _zed->addCfgValset(UBLOX_CFG_UART2OUTPROT_UBX, 0); + response &= _zed->addCfgValset(UBLOX_CFG_UART2OUTPROT_NMEA, 0); + if (commandSupported(UBLOX_CFG_UART2OUTPROT_RTCM3X)) + response &= _zed->addCfgValset(UBLOX_CFG_UART2OUTPROT_RTCM3X, 1); + response &= _zed->addCfgValset(UBLOX_CFG_UART2INPROT_UBX, settings.enableUART2UBXIn); + response &= _zed->addCfgValset(UBLOX_CFG_UART2INPROT_NMEA, 0); + response &= _zed->addCfgValset(UBLOX_CFG_UART2INPROT_RTCM3X, 1); + if (commandSupported(UBLOX_CFG_UART2INPROT_SPARTN)) + response &= _zed->addCfgValset(UBLOX_CFG_UART2INPROT_SPARTN, 0); + + // We don't want NMEA over I2C, but we will want to deliver RTCM, and UBX+RTCM is not an option + response &= _zed->addCfgValset(UBLOX_CFG_I2COUTPROT_UBX, 1); + response &= _zed->addCfgValset(UBLOX_CFG_I2COUTPROT_NMEA, 1); + if (commandSupported(UBLOX_CFG_I2COUTPROT_RTCM3X)) + response &= _zed->addCfgValset(UBLOX_CFG_I2COUTPROT_RTCM3X, 1); + + response &= _zed->addCfgValset(UBLOX_CFG_I2CINPROT_UBX, 1); + response &= _zed->addCfgValset(UBLOX_CFG_I2CINPROT_NMEA, 1); + response &= _zed->addCfgValset(UBLOX_CFG_I2CINPROT_RTCM3X, 1); + + if (commandSupported(UBLOX_CFG_I2CINPROT_SPARTN)) + { + if (present.lband_neo) + response &= + _zed->addCfgValset(UBLOX_CFG_I2CINPROT_SPARTN, + 1); // We push NEO-D9S correction data (SPARTN) to ZED-F9P over the I2C interface + else + response &= _zed->addCfgValset(UBLOX_CFG_I2CINPROT_SPARTN, 0); + } + + // The USB port on the ZED may be used for RTCM to/from the computer (as an NTRIP caster or client) + // So let's be sure all protocols are on for the USB port + response &= _zed->addCfgValset(UBLOX_CFG_USBOUTPROT_UBX, 1); + response &= _zed->addCfgValset(UBLOX_CFG_USBOUTPROT_NMEA, 1); + if (commandSupported(UBLOX_CFG_USBOUTPROT_RTCM3X)) + response &= _zed->addCfgValset(UBLOX_CFG_USBOUTPROT_RTCM3X, 1); + response &= _zed->addCfgValset(UBLOX_CFG_USBINPROT_UBX, 1); + response &= _zed->addCfgValset(UBLOX_CFG_USBINPROT_NMEA, 1); + response &= _zed->addCfgValset(UBLOX_CFG_USBINPROT_RTCM3X, 1); + if (commandSupported(UBLOX_CFG_USBINPROT_SPARTN)) + response &= _zed->addCfgValset(UBLOX_CFG_USBINPROT_SPARTN, 0); + + if (commandSupported(UBLOX_CFG_NAVSPG_INFIL_MINCNO)) + { + response &= + _zed->addCfgValset(UBLOX_CFG_NAVSPG_INFIL_MINCNO, + settings.minCNO); // Set minimum satellite signal level for navigation - default 6 + } + + if (commandSupported(UBLOX_CFG_NAV2_OUT_ENABLED)) + { + // Count NAV2 messages and enable NAV2 as needed. + if (getNAV2MessageCount() > 0) + { + response &= _zed->addCfgValset( + UBLOX_CFG_NAV2_OUT_ENABLED, + 1); // Enable NAV2 messages. This has the side effect of causing RTCM to generate twice as fast. + } + else + response &= _zed->addCfgValset(UBLOX_CFG_NAV2_OUT_ENABLED, 0); // Disable NAV2 messages + } + + response &= _zed->sendCfgValset(); + + if (response == false) + systemPrintln("Module failed config block 0"); + response = true; // Reset + + // Enable the constellations the user has set + response &= setConstellations(); // 19 messages. Send newCfg or sendCfg with value set + if (response == false) + systemPrintln("Module failed config block 1"); + response = true; // Reset + + // Make sure the appropriate messages are enabled + response &= setMessages(MAX_SET_MESSAGES_RETRIES); // Does a complete open/closed val set + if (response == false) + systemPrintln("Module failed config block 2"); + response = true; // Reset + + // Disable NMEA messages on all but UART1 + response &= _zed->newCfgValset(); + + response &= _zed->addCfgValset(UBLOX_CFG_MSGOUT_NMEA_ID_GGA_I2C, 0); + response &= _zed->addCfgValset(UBLOX_CFG_MSGOUT_NMEA_ID_GSA_I2C, 0); + response &= _zed->addCfgValset(UBLOX_CFG_MSGOUT_NMEA_ID_GSV_I2C, 0); + response &= _zed->addCfgValset(UBLOX_CFG_MSGOUT_NMEA_ID_RMC_I2C, 0); + response &= _zed->addCfgValset(UBLOX_CFG_MSGOUT_NMEA_ID_GST_I2C, 0); + response &= _zed->addCfgValset(UBLOX_CFG_MSGOUT_NMEA_ID_GLL_I2C, 0); + response &= _zed->addCfgValset(UBLOX_CFG_MSGOUT_NMEA_ID_VTG_I2C, 0); + + response &= _zed->addCfgValset(UBLOX_CFG_MSGOUT_NMEA_ID_GGA_UART2, 0); + response &= _zed->addCfgValset(UBLOX_CFG_MSGOUT_NMEA_ID_GSA_UART2, 0); + response &= _zed->addCfgValset(UBLOX_CFG_MSGOUT_NMEA_ID_GSV_UART2, 0); + response &= _zed->addCfgValset(UBLOX_CFG_MSGOUT_NMEA_ID_RMC_UART2, 0); + response &= _zed->addCfgValset(UBLOX_CFG_MSGOUT_NMEA_ID_GST_UART2, 0); + response &= _zed->addCfgValset(UBLOX_CFG_MSGOUT_NMEA_ID_GLL_UART2, 0); + response &= _zed->addCfgValset(UBLOX_CFG_MSGOUT_NMEA_ID_VTG_UART2, 0); + + response &= _zed->addCfgValset(UBLOX_CFG_MSGOUT_NMEA_ID_GGA_SPI, 0); + response &= _zed->addCfgValset(UBLOX_CFG_MSGOUT_NMEA_ID_GSA_SPI, 0); + response &= _zed->addCfgValset(UBLOX_CFG_MSGOUT_NMEA_ID_GSV_SPI, 0); + response &= _zed->addCfgValset(UBLOX_CFG_MSGOUT_NMEA_ID_RMC_SPI, 0); + response &= _zed->addCfgValset(UBLOX_CFG_MSGOUT_NMEA_ID_GST_SPI, 0); + response &= _zed->addCfgValset(UBLOX_CFG_MSGOUT_NMEA_ID_GLL_SPI, 0); + response &= _zed->addCfgValset(UBLOX_CFG_MSGOUT_NMEA_ID_VTG_SPI, 0); + + response &= _zed->sendCfgValset(); + + if (response == false) + systemPrintln("Module failed config block 3"); + + if (present.antennaShortOpen) + { + _zed->newCfgValset(); + + _zed->addCfgValset(UBLOX_CFG_HW_ANT_CFG_SHORTDET, 1); // Enable antenna short detection + _zed->addCfgValset(UBLOX_CFG_HW_ANT_CFG_OPENDET, 1); // Enable antenna open detection + + if (_zed->sendCfgValset()) + { + _zed->setAutoMONHWcallbackPtr( + &storeMONHWdata); // Enable automatic MON HW messages with callback to storeMONHWdata + } + else + { + systemPrintln("Failed to configure GNSS antenna detection"); + } + } + + if (response) + systemPrintln("ZED-F9x configuration update"); + + return (response); +} + +//---------------------------------------- +// Configure specific aspects of the receiver for rover mode +//---------------------------------------- +bool GNSS_ZED::configureRover() +{ + if (online.gnss == false) + { + log_d("GNSS not online"); + return (false); + } + + // If our settings haven't changed, and this is first config since power on, trust GNSS's settings + if (settings.updateGNSSSettings == false && firstPowerOn) + { + firstPowerOn = false; // Next time user switches modes, new settings will be applied + log_d("Skipping ZED Rover configuration"); + return (true); + } + + firstPowerOn = false; // If we switch between rover/base in the future, force config of module. + + update(); // Regularly poll to get latest data + + bool success = false; + int tryNo = -1; + + // Try up to MAX_SET_MESSAGES_RETRIES times to configure the GNSS + // This corrects occasional failures seen on the Reference Station where the GNSS is connected via SPI + // instead of I2C and UART1. I believe the SETVAL ACK is occasionally missed due to the level of messages being + // processed. + while ((++tryNo < MAX_SET_MESSAGES_RETRIES) && !success) + { + bool response = true; + + // Set output rate + response &= _zed->newCfgValset(); + response &= _zed->addCfgValset(UBLOX_CFG_RATE_MEAS, settings.measurementRateMs); + response &= _zed->addCfgValset(UBLOX_CFG_RATE_NAV, settings.navigationRate); + + // Survey mode is only available on ZED-F9P modules + if (commandSupported(UBLOX_CFG_TMODE_MODE)) + response &= _zed->addCfgValset(UBLOX_CFG_TMODE_MODE, 0); // Disable survey-in mode + + response &= + _zed->addCfgValset(UBLOX_CFG_NAVSPG_DYNMODEL, (dynModel)settings.dynamicModel); // Set dynamic model + + // RTCM is only available on ZED-F9P modules + // + // For most RTK products, the GNSS is interfaced via both I2C and UART1. Configuration and PVT/HPPOS messages + // are configured over I2C. Any messages that need to be logged are output on UART1, and received by this code + // using serialGNSS-> So in Rover mode, we want to disable any RTCM messages on I2C (and USB and UART2). + // + // But, on the Reference Station, the GNSS is interfaced via SPI. It has no access to I2C and UART1. So for that + // product - in Rover mode - we want to leave any RTCM messages enabled on SPI so they can be logged if desired. + + // Find first RTCM record in ubxMessage array + int firstRTCMRecord = getMessageNumberByName("RTCM_1005"); + + // Set RTCM messages to user's settings + for (int x = 0; x < MAX_UBX_MSG_RTCM; x++) + response &= + _zed->addCfgValset(ubxMessages[firstRTCMRecord + x].msgConfigKey - 1, + settings.ubxMessageRates[firstRTCMRecord + x]); // UBLOX_CFG UART1 - 1 = I2C + + // Set RTCM messages to user's settings + for (int x = 0; x < MAX_UBX_MSG_RTCM; x++) + { + response &= + _zed->addCfgValset(ubxMessages[firstRTCMRecord + x].msgConfigKey + 1, + settings.ubxMessageRates[firstRTCMRecord + x]); // UBLOX_CFG UART1 + 1 = UART2 + response &= + _zed->addCfgValset(ubxMessages[firstRTCMRecord + x].msgConfigKey + 2, + settings.ubxMessageRates[firstRTCMRecord + x]); // UBLOX_CFG UART1 + 2 = USB + } + + response &= _zed->addCfgValset(UBLOX_CFG_NMEA_MAINTALKERID, + 3); // Return talker ID to GNGGA after NTRIP Client set to GPGGA + + response &= _zed->addCfgValset(UBLOX_CFG_NMEA_HIGHPREC, 1); // Enable high precision NMEA + response &= _zed->addCfgValset(UBLOX_CFG_NMEA_SVNUMBERING, 1); // Enable extended satellite numbering + + response &= _zed->addCfgValset(UBLOX_CFG_NAVSPG_INFIL_MINELEV, settings.minElev); // Set minimum elevation + + response &= _zed->sendCfgValset(); // Closing + + if (response) + success = true; + } + + if (!success) + log_d("Rover config failed 1"); + + if (!success) + systemPrintln("Rover config fail"); + + return (success); +} + +//---------------------------------------- +void GNSS_ZED::debuggingDisable() +{ + if (online.gnss) + _zed->disableDebugging(); +} + +//---------------------------------------- +void GNSS_ZED::debuggingEnable() +{ + if (online.gnss) + // Enable only the critical debug messages over Serial + _zed->enableDebugging(Serial, true); +} + +//---------------------------------------- +void GNSS_ZED::enableGgaForNtrip() +{ + if (online.gnss) + { + // Set the Main Talker ID to "GP". The NMEA GGA messages will be GPGGA instead of GNGGA + _zed->setVal8(UBLOX_CFG_NMEA_MAINTALKERID, 1); + _zed->setNMEAGPGGAcallbackPtr(&pushGPGGA); // Set up the callback for GPGGA + + float measurementFrequency = (1000.0 / settings.measurementRateMs) / settings.navigationRate; + if (measurementFrequency < 0.2) + measurementFrequency = 0.2; // 0.2Hz * 5 = 1 measurement every 5 seconds + log_d("Adjusting GGA setting to %f", measurementFrequency); + _zed->setVal8(UBLOX_CFG_MSGOUT_NMEA_ID_GGA_I2C, + measurementFrequency); // Enable GGA over I2C. Tell the module to output GGA every second + } +} + +//---------------------------------------- +// Enable RTCM 1230. This is the GLONASS bias sentence and is transmitted +// even if there is no GPS fix. We use it to test serial output. +// Returns true if successfully started and false upon failure +//---------------------------------------- +bool GNSS_ZED::enableRTCMTest() +{ + if (online.gnss) + { + _zed->newCfgValset(); // Create a new Configuration Item VALSET message + _zed->addCfgValset(UBLOX_CFG_MSGOUT_RTCM_3X_TYPE1230_UART2, 1); // Enable message 1230 every second + _zed->sendCfgValset(); // Send the VALSET + return true; + } + return false; +} + +//---------------------------------------- +// Restore the GNSS to the factory settings +//---------------------------------------- +void GNSS_ZED::factoryReset() +{ + if (online.gnss) + { + _zed->factoryDefault(); // Reset everything: baud rate, I2C address, update rate, everything. And save to BBR. + _zed->saveConfiguration(); + _zed->hardReset(); // Perform a reset leading to a cold start (zero info start-up) + } +} + +//---------------------------------------- +uint16_t GNSS_ZED::fileBufferAvailable() +{ + if (online.gnss) + return (_zed->fileBufferAvailable()); + return (0); +} + +//---------------------------------------- +uint16_t GNSS_ZED::fileBufferExtractData(uint8_t *fileBuffer, int fileBytesToRead) +{ + if (online.gnss) + { + _zed->extractFileBufferData(fileBuffer, + fileBytesToRead); // TODO Does extractFileBufferData not return the bytes read? + return (1); + } + return (0); +} + +//---------------------------------------- +// Start the base using fixed coordinates +//---------------------------------------- +bool GNSS_ZED::fixedBaseStart() +{ + bool response = true; + + if (online.gnss == false) + { + log_d("GNSS not online"); + return (false); + } + + if (settings.fixedBaseCoordinateType == COORD_TYPE_ECEF) + { + // Break ECEF into main and high precision parts + // The type casting should not effect rounding of original double cast coordinate + long majorEcefX = floor((settings.fixedEcefX * 100.0) + 0.5); + long minorEcefX = floor((((settings.fixedEcefX * 100.0) - majorEcefX) * 100.0) + 0.5); + long majorEcefY = floor((settings.fixedEcefY * 100) + 0.5); + long minorEcefY = floor((((settings.fixedEcefY * 100.0) - majorEcefY) * 100.0) + 0.5); + long majorEcefZ = floor((settings.fixedEcefZ * 100) + 0.5); + long minorEcefZ = floor((((settings.fixedEcefZ * 100.0) - majorEcefZ) * 100.0) + 0.5); + + // systemPrintf("fixedEcefY (should be -4716808.5807): %0.04f\r\n", settings.fixedEcefY); + // systemPrintf("major (should be -471680858): %ld\r\n", majorEcefY); + // systemPrintf("minor (should be -7): %ld\r\n", minorEcefY); + + // Units are cm with a high precision extension so -1234.5678 should be called: (-123456, -78) + //-1280208.308,-4716803.847,4086665.811 is SparkFun HQ so... + + response &= _zed->newCfgValset(); + response &= _zed->addCfgValset(UBLOX_CFG_TMODE_MODE, 2); // Fixed + response &= _zed->addCfgValset(UBLOX_CFG_TMODE_POS_TYPE, 0); // Position in ECEF + response &= _zed->addCfgValset(UBLOX_CFG_TMODE_ECEF_X, majorEcefX); + response &= _zed->addCfgValset(UBLOX_CFG_TMODE_ECEF_X_HP, minorEcefX); + response &= _zed->addCfgValset(UBLOX_CFG_TMODE_ECEF_Y, majorEcefY); + response &= _zed->addCfgValset(UBLOX_CFG_TMODE_ECEF_Y_HP, minorEcefY); + response &= _zed->addCfgValset(UBLOX_CFG_TMODE_ECEF_Z, majorEcefZ); + response &= _zed->addCfgValset(UBLOX_CFG_TMODE_ECEF_Z_HP, minorEcefZ); + response &= _zed->sendCfgValset(); + } + else if (settings.fixedBaseCoordinateType == COORD_TYPE_GEODETIC) + { + // Add height of instrument (HI) to fixed altitude + // https://www.e-education.psu.edu/geog862/node/1853 + // For example, if HAE is at 100.0m, + 2m stick + 73mm ARP = 102.073 + float totalFixedAltitude = + settings.fixedAltitude + ((settings.antennaHeight_mm + settings.antennaPhaseCenter_mm) / 1000.0); + + // Break coordinates into main and high precision parts + // The type casting should not effect rounding of original double cast coordinate + int64_t majorLat = settings.fixedLat * 10000000; + int64_t minorLat = ((settings.fixedLat * 10000000) - majorLat) * 100; + int64_t majorLong = settings.fixedLong * 10000000; + int64_t minorLong = ((settings.fixedLong * 10000000) - majorLong) * 100; + int32_t majorAlt = totalFixedAltitude * 100; + int32_t minorAlt = ((totalFixedAltitude * 100) - majorAlt) * 100; + + // systemPrintf("fixedLong (should be -105.184774720): %0.09f\r\n", settings.fixedLong); + // systemPrintf("major (should be -1051847747): %lld\r\n", majorLat); + // systemPrintf("minor (should be -20): %lld\r\n", minorLat); + // + // systemPrintf("fixedLat (should be 40.090335429): %0.09f\r\n", settings.fixedLat); + // systemPrintf("major (should be 400903354): %lld\r\n", majorLong); + // systemPrintf("minor (should be 29): %lld\r\n", minorLong); + // + // systemPrintf("fixedAlt (should be 1560.2284): %0.04f\r\n", settings.fixedAltitude); + // systemPrintf("major (should be 156022): %ld\r\n", majorAlt); + // systemPrintf("minor (should be 84): %ld\r\n", minorAlt); + + response &= _zed->newCfgValset(); + response &= _zed->addCfgValset(UBLOX_CFG_TMODE_MODE, 2); // Fixed + response &= _zed->addCfgValset(UBLOX_CFG_TMODE_POS_TYPE, 1); // Position in LLH + response &= _zed->addCfgValset(UBLOX_CFG_TMODE_LAT, majorLat); + response &= _zed->addCfgValset(UBLOX_CFG_TMODE_LAT_HP, minorLat); + response &= _zed->addCfgValset(UBLOX_CFG_TMODE_LON, majorLong); + response &= _zed->addCfgValset(UBLOX_CFG_TMODE_LON_HP, minorLong); + response &= _zed->addCfgValset(UBLOX_CFG_TMODE_HEIGHT, majorAlt); + response &= _zed->addCfgValset(UBLOX_CFG_TMODE_HEIGHT_HP, minorAlt); + response &= _zed->sendCfgValset(); + } + + return (response); +} + +//---------------------------------------- +// Return the number of active/enabled messages +//---------------------------------------- +uint8_t GNSS_ZED::getActiveMessageCount() +{ + uint8_t count = 0; + + for (int x = 0; x < MAX_UBX_MSG; x++) + if (settings.ubxMessageRates[x] > 0) + count++; + return (count); +} + +//---------------------------------------- +// Returns the altitude in meters or zero if the GNSS is offline +//---------------------------------------- +double GNSS_ZED::getAltitude() +{ + return _altitude; +} + +//---------------------------------------- +// Returns the carrier solution or zero if not online +//---------------------------------------- +uint8_t GNSS_ZED::getCarrierSolution() +{ + return (_carrierSolution); +} + +//---------------------------------------- +uint32_t GNSS_ZED::getDataBaudRate() +{ + if (online.gnss) + return _zed->getVal32(UBLOX_CFG_UART1_BAUDRATE); + return (0); +} + +//---------------------------------------- +// Returns the day number or zero if not online +//---------------------------------------- +uint8_t GNSS_ZED::getDay() +{ + return (_day); +} + +//---------------------------------------- +// Return the number of milliseconds since GNSS data was last updated +//---------------------------------------- +uint16_t GNSS_ZED::getFixAgeMilliseconds() +{ + return (millis() - _pvtArrivalMillis); +} + +//---------------------------------------- +// Returns the fix type or zero if not online +//---------------------------------------- +uint8_t GNSS_ZED::getFixType() +{ + return (_fixType); +} + +//---------------------------------------- +// Get the horizontal position accuracy +// Returns the horizontal position accuracy or zero if offline +//---------------------------------------- +float GNSS_ZED::getHorizontalAccuracy() +{ + return (_horizontalAccuracy); +} + +//---------------------------------------- +// Returns the hours of 24 hour clock or zero if not online +//---------------------------------------- +uint8_t GNSS_ZED::getHour() +{ + return (_hour); +} + +//---------------------------------------- +const char * GNSS_ZED::getId() +{ + if (online.gnss) + return (gnssUniqueId); + return ((char *)"\0"); +} + +//---------------------------------------- +// Get the latitude value +// Returns the latitude value or zero if not online +//---------------------------------------- +double GNSS_ZED::getLatitude() +{ + return (_latitude); +} + +//---------------------------------------- +// Query GNSS for current leap seconds +//---------------------------------------- +uint8_t GNSS_ZED::getLeapSeconds() +{ + if (online.gnss) + { + sfe_ublox_ls_src_e leapSecSource; + _leapSeconds = _zed->getCurrentLeapSeconds(leapSecSource); + return (_leapSeconds); + } + return (18); // Default to 18 if GNSS is offline +} + +//---------------------------------------- +// Get the longitude value +// Outputs: +// Returns the longitude value or zero if not online +//---------------------------------------- +double GNSS_ZED::getLongitude() +{ + return (_longitude); +} + +//---------------------------------------- +// Given the name of a message, return the array number +//---------------------------------------- +uint8_t GNSS_ZED::getMessageNumberByName(const char *msgName) +{ + if (present.gnss_zedf9p) + { + for (int x = 0; x < MAX_UBX_MSG; x++) + { + if (strcmp(ubxMessages[x].msgTextName, msgName) == 0) + return (x); + } + } + else + systemPrintln("getMessageNumberByName() Platform not supported"); + + systemPrintf("getMessageNumberByName: %s not found\r\n", msgName); + return (0); +} + +//---------------------------------------- +// Given the name of a message, find it, and return the rate +//---------------------------------------- +uint8_t GNSS_ZED::getMessageRateByName(const char *msgName) +{ + return (settings.ubxMessageRates[getMessageNumberByName(msgName)]); +} + +//---------------------------------------- +// Returns two digits of milliseconds or zero if not online +//---------------------------------------- +uint8_t GNSS_ZED::getMillisecond() +{ + return (_millisecond); +} + +//---------------------------------------- +// Returns minutes or zero if not online +//---------------------------------------- +uint8_t GNSS_ZED::getMinute() +{ + return (_minute); +} + +//---------------------------------------- +// Returns month number or zero if not online +//---------------------------------------- +uint8_t GNSS_ZED::getMonth() +{ + return (_month); +} + +//---------------------------------------- +// Returns nanoseconds or zero if not online +//---------------------------------------- +uint32_t GNSS_ZED::getNanosecond() +{ + return (_nanosecond); +} + +//---------------------------------------- +// Count the number of NAV2 messages with rates more than 0. Used for determining if we need the enable +// the global NAV2 feature. +//---------------------------------------- +uint8_t GNSS_ZED::getNAV2MessageCount() +{ + int enabledMessages = 0; + int startOfBlock = 0; + int endOfBlock = 0; + + setMessageOffsets(&ubxMessages[0], "NAV2", startOfBlock, + endOfBlock); // Find start and stop of given messageType in message array + + for (int x = 0; x < (endOfBlock - startOfBlock); x++) + { + if (settings.ubxMessageRates[x + startOfBlock] > 0) + enabledMessages++; + } + + setMessageOffsets(&ubxMessages[0], "NMEANAV2", startOfBlock, + endOfBlock); // Find start and stop of given messageType in message array + + for (int x = 0; x < (endOfBlock - startOfBlock); x++) + { + if (settings.ubxMessageRates[x + startOfBlock] > 0) + enabledMessages++; + } + + return (enabledMessages); +} + +//---------------------------------------- +uint32_t GNSS_ZED::getRadioBaudRate() +{ + if (online.gnss) + return _zed->getVal32(UBLOX_CFG_UART2_BAUDRATE); + return (0); +} + +//---------------------------------------- +// Returns the seconds between measurements +//---------------------------------------- +double GNSS_ZED::getRateS() +{ + // Because we may be in base mode, do not get freq from module, use settings instead + float measurementFrequency = (1000.0 / settings.measurementRateMs) / settings.navigationRate; + double measurementRateS = 1.0 / measurementFrequency; // 1 / 4Hz = 0.25s + + return (measurementRateS); +} + +//---------------------------------------- +const char * GNSS_ZED::getRtcmDefaultString() +{ + return ((char *)"1005/1074/1084/1094/1124 1Hz & 1230 0.1Hz"); +} + +//---------------------------------------- +const char * GNSS_ZED::getRtcmLowDataRateString() +{ + return ((char *)"1074/1084/1094/1124 0.5Hz & 1005/1230 0.1Hz"); +} + +//---------------------------------------- +// Returns the number of satellites in view or zero if offline +//---------------------------------------- +uint8_t GNSS_ZED::getSatellitesInView() +{ + return (_satellitesInView); +} + +//---------------------------------------- +// Returns seconds or zero if not online +//---------------------------------------- +uint8_t GNSS_ZED::getSecond() +{ + return (_second); +} + +//---------------------------------------- +// Get the survey-in mean accuracy +//---------------------------------------- +float GNSS_ZED::getSurveyInMeanAccuracy() +{ + static float svinMeanAccuracy = 0; + static unsigned long lastCheck = 0; + + if (online.gnss == false) + return (0); + + // Use a local static so we don't have to request these values multiple times (ZED takes many ms to respond + // to this command) + if (millis() - lastCheck > 1000) + { + lastCheck = millis(); + svinMeanAccuracy = _zed->getSurveyInMeanAccuracy(50); + } + return (svinMeanAccuracy); +} + +//---------------------------------------- +// Return the number of seconds the survey-in process has been running +//---------------------------------------- +int GNSS_ZED::getSurveyInObservationTime() +{ + static uint16_t svinObservationTime = 0; + static unsigned long lastCheck = 0; + + if (online.gnss == false) + return (0); + + // Use a local static so we don't have to request these values multiple times (ZED takes many ms to respond + // to this command) + if (millis() - lastCheck > 1000) + { + lastCheck = millis(); + svinObservationTime = _zed->getSurveyInObservationTime(50); + } + return (svinObservationTime); +} + +//---------------------------------------- +// Returns timing accuracy or zero if not online +//---------------------------------------- +uint32_t GNSS_ZED::getTimeAccuracy() +{ + return (_tAcc); +} + +//---------------------------------------- +// Returns full year, ie 2023, not 23. +//---------------------------------------- +uint16_t GNSS_ZED::getYear() +{ + return (_year); +} + +//---------------------------------------- +bool GNSS_ZED::isBlocking() +{ + return (false); +} + +//---------------------------------------- +// Date is confirmed once we have GNSS fix +//---------------------------------------- +bool GNSS_ZED::isConfirmedDate() +{ + return (_confirmedDate); +} + +//---------------------------------------- +// Time is confirmed once we have GNSS fix +//---------------------------------------- +bool GNSS_ZED::isConfirmedTime() +{ + return (_confirmedTime); +} + +//---------------------------------------- +// Return true if GNSS receiver has a higher quality DGPS fix than 3D +//---------------------------------------- +bool GNSS_ZED::isDgpsFixed() +{ + // Not supported + return (false); +} + +//---------------------------------------- +// Some functions (L-Band area frequency determination) merely need to know if we have a valid fix, not what type of fix +// This function checks to see if the given platform has reached sufficient fix type to be considered valid +//---------------------------------------- +bool GNSS_ZED::isFixed() +{ + // 0 = no fix + // 1 = dead reckoning only + // 2 = 2D-fix + // 3 = 3D-fix + // 4 = GNSS + dead reckoning combined + // 5 = time only fix + return (_fixType >= 3); +} + +//---------------------------------------- +// Used in tpISR() for time pulse synchronization +//---------------------------------------- +bool GNSS_ZED::isFullyResolved() +{ + return (_fullyResolved); +} + +//---------------------------------------- +bool GNSS_ZED::isPppConverged() +{ + return (false); +} + +//---------------------------------------- +bool GNSS_ZED::isPppConverging() +{ + return (false); +} + +//---------------------------------------- +// Some functions (L-Band area frequency determination) merely need to +// know if we have an RTK Fix. This function checks to see if the given +// platform has reached sufficient fix type to be considered valid +//---------------------------------------- +bool GNSS_ZED::isRTKFix() +{ + // 0 = No RTK + // 1 = RTK Float + // 2 = RTK Fix + return (_carrierSolution == 2); +} + +//---------------------------------------- +// Some functions (L-Band area frequency determination) merely need to +// know if we have an RTK Float. This function checks to see if the +// given platform has reached sufficient fix type to be considered valid +//---------------------------------------- +bool GNSS_ZED::isRTKFloat() +{ + // 0 = No RTK + // 1 = RTK Float + // 2 = RTK Fix + return (_carrierSolution == 1); +} + +//---------------------------------------- +// Determine if the survey-in operation is complete +//---------------------------------------- +bool GNSS_ZED::isSurveyInComplete() +{ + if (online.gnss) + return (_zed->getSurveyInValid(50)); + return (false); +} + +//---------------------------------------- +// Date will be valid if the RTC is reporting (regardless of GNSS fix) +//---------------------------------------- +bool GNSS_ZED::isValidDate() +{ + return (_validDate); +} + +//---------------------------------------- +// Time will be valid if the RTC is reporting (regardless of GNSS fix) +//---------------------------------------- +bool GNSS_ZED::isValidTime() +{ + return (_validTime); +} + +//---------------------------------------- +// Disable data output from the NEO +//---------------------------------------- +bool GNSS_ZED::lBandCommunicationDisable() +{ + bool response = true; + +#ifdef COMPILE_L_BAND + + response &= _zed->setRXMCORcallbackPtr( + nullptr); // Disable callback to check if the PMP data is being decrypted successfully + + response &= i2cLBand.setRXMPMPmessageCallbackPtr(nullptr); // Disable PMP callback no matter the platform + + if (present.lband_neo) + { + response &= i2cLBand.newCfgValset(); + + response &= + i2cLBand.addCfgValset(UBLOX_CFG_MSGOUT_UBX_RXM_PMP_I2C, 0); // Disable UBX-RXM-PMP from NEO's I2C port + + // TODO: change this as needed for Facet v2 + response &= i2cLBand.addCfgValset(UBLOX_CFG_UART2OUTPROT_UBX, 0); // Disable UBX output from NEO's UART2 + + // TODO: change this as needed for Facet v2 + response &= i2cLBand.addCfgValset(UBLOX_CFG_MSGOUT_UBX_RXM_PMP_UART2, 0); // Disable UBX-RXM-PMP on NEO's UART2 + + response &= i2cLBand.sendCfgValset(); + } + else + { + systemPrintln("zedEnableLBandCorrections: Unknown platform"); + return (false); + } + +#endif + + return (response); +} + +//---------------------------------------- +// Enable data output from the NEO +//---------------------------------------- +bool GNSS_ZED::lBandCommunicationEnable() +{ + /* + Paul's Notes on (NEO-D9S) L-Band: + + Torch will receive PointPerfect SPARTN via IP, run it through the PPL, and feed RTCM to the UM980. No L-Band... + + The EVK has ZED-F9P and NEO-D9S. But there are two versions of the PCB: + v1.1 PCB : + Both ZED and NEO are on the i2c_0 I2C bus (the OLED is on i2c_1) + ZED UART1 is connected to the ESP32 (pins 25 and 33) only + ZED UART2 is connected to the I/O connector only + NEO UART1 is connected to test points only + NEO UART2 is not connected + v1.0 PCB (the one we are currently using for code development) : + Both ZED and NEO are on the i2c_0 I2C bus + ZED UART1 is connected to NEO UART1 only - not to ESP32 (Big mistake! Makes BT and Logging much more + complicated...) ZED UART2 is connected to the I/O connector only NEO UART2 is not connected + + Facet v2 hasn't been built yet. The v2.01 PCB probably won't get built as it needs the new soft power switch. + When v2.10 (?) gets built : + Both ZED and NEO are on the I2C bus + ZED UART1 is connected to the ESP32 (pins 14 and 13) and also to the DATA connector via the Mux (output + only) ZED UART2 is connected to the RADIO connector only NEO UART1 is not connected NEO UART2 TX is connected to + ESP32 pin 4. If the ESP32 has a UART spare - and it probably does - the PMP data can go over this connection and + avoid having double-PMP traffic on I2C. Neat huh?! + + Facet mosaic v1.0 PCB has been built, but needs the new soft power switch and some other minor mods. + X5 COM1 is connected to the ESP32 (pins 13 and 14) - RTCM from PPL, NMEA and RTCM to PPL and Bluetooth + X5 COM2 is connected to the RADIO connector only + X5 COM3 is connected to the DATA connector via the Mux (I/O) + X5 COM4 is connected to the ESP32 (pins 4 and 25) - raw L-Band to PPL, control from ESP32 to X5 ? + + So, what does all of this mean? + EVK v1.0 supports direct NEO to ZED UART communication, but V1.1 will not. There is no point in supporting it + here. Facet v2 can pipe NEO UART PMP data to the ZED (over I2C or maybe UART), but the hardware doesn't exist yet + so there is no point in adding that feature yet... TODO. So, right now, we should assume NEO PMP data will arrive + via I2C, and will get pushed to the ZED via I2C if the corrections priority permits. Deleting: + useI2cForLbandCorrections, useI2cForLbandCorrectionsConfigured and rtcmTimeoutBeforeUsingLBand_s + */ + + bool response = true; + +#ifdef COMPILE_L_BAND + + response &= _zed->setRXMCORcallbackPtr( + &checkRXMCOR); // Enable callback to check if the PMP data is being decrypted successfully + + if (present.lband_neo) + { + response &= i2cLBand.setRXMPMPmessageCallbackPtr(&pushRXMPMP); // Enable PMP callback to push raw PMP over I2C + + response &= i2cLBand.newCfgValset(); + + response &= i2cLBand.addCfgValset(UBLOX_CFG_MSGOUT_UBX_RXM_PMP_I2C, 1); // Enable UBX-RXM-PMP on NEO's I2C port + + response &= i2cLBand.addCfgValset(UBLOX_CFG_UART1OUTPROT_UBX, 0); // Disable UBX output on NEO's UART1 + + response &= i2cLBand.addCfgValset(UBLOX_CFG_MSGOUT_UBX_RXM_PMP_UART1, 0); // Disable UBX-RXM-PMP on NEO's UART1 + + // TODO: change this as needed for Facet v2 + response &= i2cLBand.addCfgValset(UBLOX_CFG_UART2OUTPROT_UBX, 0); // Disable UBX output on NEO's UART2 + + // TODO: change this as needed for Facet v2 + response &= i2cLBand.addCfgValset(UBLOX_CFG_MSGOUT_UBX_RXM_PMP_UART2, 0); // Disable UBX-RXM-PMP on NEO's UART2 + + response &= i2cLBand.sendCfgValset(); + } + else + { + systemPrintln("zedEnableLBandCorrections: Unknown platform"); + return (false); + } + +#endif + + return (response); +} + +//---------------------------------------- +bool GNSS_ZED::lock(void) +{ + if (!iAmLocked) + { + iAmLocked = true; + return true; + } + + unsigned long startTime = millis(); + while (((millis() - startTime) < 2100) && (iAmLocked)) + delay(1); // Yield + + if (!iAmLocked) + { + iAmLocked = true; + return true; + } + + return false; +} + +//---------------------------------------- +bool GNSS_ZED::lockCreate(void) +{ + return true; +} + +//---------------------------------------- +void GNSS_ZED::lockDelete(void) +{ +} + +//---------------------------------------- +// Controls the constellations that are used to generate a fix and logged +//---------------------------------------- +void GNSS_ZED::menuConstellations() +{ + while (1) + { + systemPrintln(); + systemPrintln("Menu: Constellations"); + + for (int x = 0; x < MAX_UBX_CONSTELLATIONS; x++) + { + systemPrintf("%d) Constellation %s: ", x + 1, settings.ubxConstellations[x].textName); + if (settings.ubxConstellations[x].enabled) + systemPrint("Enabled"); + else + systemPrint("Disabled"); + systemPrintln(); + } + + systemPrintln("x) Exit"); + + int incoming = getUserInputNumber(); // Returns EXIT, TIMEOUT, or long + + if (incoming >= 1 && incoming <= MAX_UBX_CONSTELLATIONS) + { + incoming--; // Align choice to constellation array of 0 to 5 + + settings.ubxConstellations[incoming].enabled ^= 1; + + // 3.10.6: To avoid cross-correlation issues, it is recommended that GPS and QZSS are always both enabled or + // both disabled. + if (incoming == ubxConstellationIDToIndex(SFE_UBLOX_GNSS_ID_GPS)) // Match QZSS to GPS + { + settings.ubxConstellations[ubxConstellationIDToIndex(SFE_UBLOX_GNSS_ID_QZSS)].enabled = + settings.ubxConstellations[incoming].enabled; + } + if (incoming == ubxConstellationIDToIndex(SFE_UBLOX_GNSS_ID_QZSS)) // Match GPS to QZSS + { + settings.ubxConstellations[ubxConstellationIDToIndex(SFE_UBLOX_GNSS_ID_GPS)].enabled = + settings.ubxConstellations[incoming].enabled; + } + } + else if (incoming == INPUT_RESPONSE_GETNUMBER_EXIT) + break; + else if (incoming == INPUT_RESPONSE_GETNUMBER_TIMEOUT) + break; + else + printUnknown(incoming); + } + + // Apply current settings to module + setConstellations(); + + clearBuffer(); // Empty buffer of any newline chars +} + +//---------------------------------------- +void GNSS_ZED::menuMessageBaseRtcm() +{ + zedMenuMessagesSubtype(settings.ubxMessageRatesBase, "RTCM-Base"); +} + +//---------------------------------------- +// Control the messages that get broadcast over Bluetooth and logged (if enabled) +//---------------------------------------- +void GNSS_ZED::menuMessages() +{ + while (1) + { + systemPrintln(); + systemPrintln("Menu: GNSS Messages"); + + systemPrintf("Active messages: %d\r\n", getActiveMessageCount()); + + systemPrintln("1) Set NMEA Messages"); + systemPrintln("2) Set RTCM Messages"); + systemPrintln("3) Set RXM Messages"); + systemPrintln("4) Set NAV Messages"); + systemPrintln("5) Set NAV2 Messages"); + systemPrintln("6) Set NMEA NAV2 Messages"); + systemPrintln("7) Set MON Messages"); + systemPrintln("8) Set TIM Messages"); + systemPrintln("9) Set PUBX Messages"); + + systemPrintln("10) Reset to Surveying Defaults (NMEAx5)"); + systemPrintln("11) Reset to PPP Logging Defaults (NMEAx5 + RXMx2)"); + systemPrintln("12) Turn off all messages"); + systemPrintln("13) Turn on all messages"); + + systemPrintln("x) Exit"); + + int incoming = getUserInputNumber(); // Returns EXIT, TIMEOUT, or long + + if (incoming == 1) + zedMenuMessagesSubtype(settings.ubxMessageRates, + "NMEA_"); // The following _ avoids listing NMEANAV2 messages + else if (incoming == 2) + zedMenuMessagesSubtype(settings.ubxMessageRates, "RTCM"); + else if (incoming == 3) + zedMenuMessagesSubtype(settings.ubxMessageRates, "RXM"); + else if (incoming == 4) + zedMenuMessagesSubtype(settings.ubxMessageRates, "NAV_"); // The following _ avoids listing NAV2 messages + else if (incoming == 5) + zedMenuMessagesSubtype(settings.ubxMessageRates, "NAV2"); + else if (incoming == 6) + zedMenuMessagesSubtype(settings.ubxMessageRates, "NMEANAV2"); + else if (incoming == 7) + zedMenuMessagesSubtype(settings.ubxMessageRates, "MON"); + else if (incoming == 8) + zedMenuMessagesSubtype(settings.ubxMessageRates, "TIM"); + else if (incoming == 9) + zedMenuMessagesSubtype(settings.ubxMessageRates, "PUBX"); + else if (incoming == 10) + { + setGNSSMessageRates(settings.ubxMessageRates, 0); // Turn off all messages + setMessageRateByName("NMEA_GGA", 1); + setMessageRateByName("NMEA_GSA", 1); + setMessageRateByName("NMEA_GST", 1); + + // We want GSV NMEA to be reported at 1Hz to avoid swamping SPP connection + float measurementFrequency = (1000.0 / settings.measurementRateMs) / settings.navigationRate; + if (measurementFrequency < 1.0) + measurementFrequency = 1.0; + setMessageRateByName("NMEA_GSV", measurementFrequency); // One report per second + + setMessageRateByName("NMEA_RMC", 1); + systemPrintln("Reset to Surveying Defaults (NMEAx5)"); + } + else if (incoming == 11) + { + setGNSSMessageRates(settings.ubxMessageRates, 0); // Turn off all messages + setMessageRateByName("NMEA_GGA", 1); + setMessageRateByName("NMEA_GSA", 1); + setMessageRateByName("NMEA_GST", 1); + + // We want GSV NMEA to be reported at 1Hz to avoid swamping SPP connection + float measurementFrequency = (1000.0 / settings.measurementRateMs) / settings.navigationRate; + if (measurementFrequency < 1.0) + measurementFrequency = 1.0; + setMessageRateByName("NMEA_GSV", measurementFrequency); // One report per second + + setMessageRateByName("NMEA_RMC", 1); + + setMessageRateByName("RXM_RAWX", 1); + setMessageRateByName("RXM_SFRBX", 1); + systemPrintln("Reset to PPP Logging Defaults (NMEAx5 + RXMx2)"); + } + else if (incoming == 12) + { + setGNSSMessageRates(settings.ubxMessageRates, 0); // Turn off all messages + systemPrintln("All messages disabled"); + } + else if (incoming == 13) + { + setGNSSMessageRates(settings.ubxMessageRates, 1); // Turn on all messages to report once per fix + systemPrintln("All messages enabled"); + } + else if (incoming == INPUT_RESPONSE_GETNUMBER_EXIT) + break; + else if (incoming == INPUT_RESPONSE_GETNUMBER_TIMEOUT) + break; + else + printUnknown(incoming); + } + + clearBuffer(); // Empty buffer of any newline chars + + // Make sure the appropriate messages are enabled + bool response = setMessages(MAX_SET_MESSAGES_RETRIES); // Does a complete open/closed val set + if (response == false) + systemPrintf("menuMessages: Failed to enable messages - after %d tries", MAX_SET_MESSAGES_RETRIES); + else + systemPrintln("menuMessages: Messages successfully enabled"); + + setLoggingType(); // Update Standard, PPP, or custom for icon selection +} + +//---------------------------------------- +// Print the module type and firmware version +//---------------------------------------- +void GNSS_ZED::printModuleInfo() +{ + systemPrintf("ZED-F9P firmware: %s\r\n", gnssFirmwareVersion); +} + +//---------------------------------------- +// Send correction data to the GNSS +// Returns the number of correction data bytes written +//---------------------------------------- +int GNSS_ZED::pushRawData(uint8_t *dataToSend, int dataLength) +{ + if (online.gnss) + return (_zed->pushRawData((uint8_t *)dataToSend, dataLength)); + return (0); +} + +//---------------------------------------- +uint16_t GNSS_ZED::rtcmBufferAvailable() +{ + if (online.gnss) + return (_zed->rtcmBufferAvailable()); + return (0); +} + +//---------------------------------------- +// If LBand is being used, ignore any RTCM that may come in from the GNSS +//---------------------------------------- +void GNSS_ZED::rtcmOnGnssDisable() +{ + if (online.gnss) + _zed->setUART2Input(COM_TYPE_UBX); // Set ZED's UART2 to input UBX (no RTCM) +} + +//---------------------------------------- +// If L-Band is available, but encrypted, allow RTCM through other sources (radio, ESP-Now) to GNSS receiver +//---------------------------------------- +void GNSS_ZED::rtcmOnGnssEnable() +{ + if (online.gnss) + _zed->setUART2Input(COM_TYPE_RTCM3); // Set the ZED's UART2 to input RTCM +} + +//---------------------------------------- +uint16_t GNSS_ZED::rtcmRead(uint8_t *rtcmBuffer, int rtcmBytesToRead) +{ + if (online.gnss) + return (_zed->extractRTCMBufferData(rtcmBuffer, rtcmBytesToRead)); + return (0); +} + +//---------------------------------------- +// Save the current configuration +// Returns true when the configuration was saved and false upon failure +//---------------------------------------- +bool GNSS_ZED::saveConfiguration() +{ + if (online.gnss == false) + return false; + + _zed->saveConfiguration(); // Save the current settings to flash and BBR on the ZED-F9P + return true; +} + +//---------------------------------------- +// Set the baud rate on the GNSS port that interfaces between the ESP32 and the GNSS +// This just sets the GNSS side +// Used during Bluetooth testing +//---------------------------------------- +bool GNSS_ZED::setBaudrate(uint32_t baudRate) +{ + if (online.gnss) + return _zed->setVal32(UBLOX_CFG_UART1_BAUDRATE, + (115200 * 2)); // Defaults to 230400 to maximize message output support + return false; +} + +//---------------------------------------- +// Enable all the valid constellations and bands for this platform +// Band support varies between platforms and firmware versions +// We open/close a complete set of 19 messages +//---------------------------------------- +bool GNSS_ZED::setConstellations() +{ + if (online.gnss == false) + return (false); + + bool response = true; + + response &= _zed->newCfgValset(); + + // GPS + int gnssIndex = ubxConstellationIDToIndex(SFE_UBLOX_GNSS_ID_GPS); + bool enableMe = settings.ubxConstellations[gnssIndex].enabled; + response &= _zed->addCfgValset(settings.ubxConstellations[gnssIndex].configKey, enableMe); + response &= _zed->addCfgValset(UBLOX_CFG_SIGNAL_GPS_L1CA_ENA, enableMe); + response &= _zed->addCfgValset(UBLOX_CFG_SIGNAL_GPS_L2C_ENA, enableMe); + + // SBAS + // v1.12 ZED-F9P firmware does not allow for SBAS control + // Also, if we can't identify the version (99), skip SBAS enable + if ((gnssFirmwareVersionInt == 112) || (gnssFirmwareVersionInt == 99)) + { + // Skip + } + else + { + gnssIndex = ubxConstellationIDToIndex(SFE_UBLOX_GNSS_ID_SBAS); + enableMe = settings.ubxConstellations[gnssIndex].enabled; + response &= _zed->addCfgValset(settings.ubxConstellations[gnssIndex].configKey, enableMe); + response &= _zed->addCfgValset(UBLOX_CFG_SIGNAL_SBAS_L1CA_ENA, enableMe); + } + + // GAL + gnssIndex = ubxConstellationIDToIndex(SFE_UBLOX_GNSS_ID_GALILEO); + enableMe = settings.ubxConstellations[gnssIndex].enabled; + response &= + _zed->addCfgValset(settings.ubxConstellations[gnssIndex].configKey, enableMe); + response &= _zed->addCfgValset(UBLOX_CFG_SIGNAL_GAL_E1_ENA, enableMe); + response &= _zed->addCfgValset(UBLOX_CFG_SIGNAL_GAL_E5B_ENA, enableMe); + + // BDS + gnssIndex = ubxConstellationIDToIndex(SFE_UBLOX_GNSS_ID_BEIDOU); + enableMe = settings.ubxConstellations[gnssIndex].enabled; + response &= + _zed->addCfgValset(settings.ubxConstellations[gnssIndex].configKey, enableMe); + response &= _zed->addCfgValset(UBLOX_CFG_SIGNAL_BDS_B1_ENA, enableMe); + response &= _zed->addCfgValset(UBLOX_CFG_SIGNAL_BDS_B2_ENA, enableMe); + + // QZSS + gnssIndex = ubxConstellationIDToIndex(SFE_UBLOX_GNSS_ID_QZSS); + enableMe = settings.ubxConstellations[gnssIndex].enabled; + response &= + _zed->addCfgValset(settings.ubxConstellations[gnssIndex].configKey, enableMe); + response &= _zed->addCfgValset(UBLOX_CFG_SIGNAL_QZSS_L1CA_ENA, enableMe); + // UBLOX_CFG_SIGNAL_QZSS_L1S_ENA not supported on F9R in v1.21 and below + response &= _zed->addCfgValset(UBLOX_CFG_SIGNAL_QZSS_L1S_ENA, enableMe); + response &= _zed->addCfgValset(UBLOX_CFG_SIGNAL_QZSS_L2C_ENA, enableMe); + + // GLO + gnssIndex = ubxConstellationIDToIndex(SFE_UBLOX_GNSS_ID_GLONASS); + enableMe = settings.ubxConstellations[gnssIndex].enabled; + response &= + _zed->addCfgValset(settings.ubxConstellations[gnssIndex].configKey, enableMe); + response &= _zed->addCfgValset(UBLOX_CFG_SIGNAL_GLO_L1_ENA, enableMe); + response &= _zed->addCfgValset(UBLOX_CFG_SIGNAL_GLO_L2_ENA, enableMe); + + response &= _zed->sendCfgValset(); + + return (response); +} + +//---------------------------------------- +bool GNSS_ZED::setDataBaudRate(uint32_t baud) +{ + if (online.gnss) + return _zed->setVal32(UBLOX_CFG_UART1_BAUDRATE, baud); + return false; +} + +//---------------------------------------- +// Set the elevation in degrees +//---------------------------------------- +bool GNSS_ZED::setElevation(uint8_t elevationDegrees) +{ + if (online.gnss) + { + _zed->setVal8(UBLOX_CFG_NAVSPG_INFIL_MINELEV, elevationDegrees); // Set minimum elevation + return true; + } + return false; +} + +//---------------------------------------- +// Given a unique string, find first and last records containing that string in message array +//---------------------------------------- +void GNSS_ZED::setMessageOffsets(const ubxMsg *localMessage, const char *messageType, int &startOfBlock, int &endOfBlock) +{ + char messageNamePiece[40]; // UBX_RTCM + snprintf(messageNamePiece, sizeof(messageNamePiece), "%s", messageType); // Put UBX_ infront of type + + // Find the first occurrence + for (startOfBlock = 0; startOfBlock < MAX_UBX_MSG; startOfBlock++) + { + if (strstr(localMessage[startOfBlock].msgTextName, messageNamePiece) != nullptr) + break; + } + if (startOfBlock == MAX_UBX_MSG) + { + // Error out + startOfBlock = 0; + endOfBlock = 0; + return; + } + + // Find the last occurrence + for (endOfBlock = startOfBlock + 1; endOfBlock < MAX_UBX_MSG; endOfBlock++) + { + if (strstr(localMessage[endOfBlock].msgTextName, messageNamePiece) == nullptr) + break; + } +} + +//---------------------------------------- +// Given the name of a message, find it, and set the rate +//---------------------------------------- +bool GNSS_ZED::setMessageRateByName(const char *msgName, uint8_t msgRate) +{ + for (int x = 0; x < MAX_UBX_MSG; x++) + { + if (strcmp(ubxMessages[x].msgTextName, msgName) == 0) + { + settings.ubxMessageRates[x] = msgRate; + return (true); + } + } + systemPrintf("setMessageRateByName: %s not found\r\n", msgName); + return (false); +} + +//---------------------------------------- +// Enable all the valid messages for this platform +// There are many messages so split into batches. VALSET is limited to 64 max per batch +// Uses dummy newCfg and sendCfg values to be sure we open/close a complete set +//---------------------------------------- +bool GNSS_ZED::setMessages(int maxRetries) +{ + bool success = false; + + if (online.gnss) + { + int tryNo = -1; + + // Try up to maxRetries times to configure the messages + // This corrects occasional failures seen on the Reference Station where the GNSS is connected via SPI + // instead of I2C and UART1. I believe the SETVAL ACK is occasionally missed due to the level of messages being + // processed. + while ((++tryNo < maxRetries) && !success) + { + bool response = true; + int messageNumber = 0; + + while (messageNumber < MAX_UBX_MSG) + { + response &= _zed->newCfgValset(); + + do + { + if (messageSupported(messageNumber)) + { + uint8_t rate = settings.ubxMessageRates[messageNumber]; + + response &= _zed->addCfgValset(ubxMessages[messageNumber].msgConfigKey, rate); + } + messageNumber++; + } while (((messageNumber % 43) < 42) && + (messageNumber < MAX_UBX_MSG)); // Limit 1st batch to 42. Batches after that will be (up to) 43 in + // size. It's a HHGTTG thing. + + if (_zed->sendCfgValset() == false) + { + log_d("sendCfg failed at messageNumber %d %s. Try %d of %d.", messageNumber - 1, + (messageNumber - 1) < MAX_UBX_MSG ? ubxMessages[messageNumber - 1].msgTextName : "", tryNo + 1, + maxRetries); + response &= false; // If any one of the Valset fails, report failure overall + } + } + + if (response) + success = true; + } + } + return (success); +} + +//---------------------------------------- +// Enable all the valid messages for this platform over the USB port +// Add 2 to every UART1 key. This is brittle and non-perfect, but works. +//---------------------------------------- +bool GNSS_ZED::setMessagesUsb(int maxRetries) +{ + bool success = false; + + if (online.gnss) + { + int tryNo = -1; + + // Try up to maxRetries times to configure the messages + // This corrects occasional failures seen on the Reference Station where the GNSS is connected via SPI + // instead of I2C and UART1. I believe the SETVAL ACK is occasionally missed due to the level of messages being + // processed. + while ((++tryNo < maxRetries) && !success) + { + bool response = true; + int messageNumber = 0; + + while (messageNumber < MAX_UBX_MSG) + { + response &= _zed->newCfgValset(); + + do + { + if (messageSupported(messageNumber)) + response &= _zed->addCfgValset(ubxMessages[messageNumber].msgConfigKey + 2, + settings.ubxMessageRates[messageNumber]); + messageNumber++; + } while (((messageNumber % 43) < 42) && + (messageNumber < MAX_UBX_MSG)); // Limit 1st batch to 42. Batches after that will be (up to) 43 in + // size. It's a HHGTTG thing. + + response &= _zed->sendCfgValset(); + } + + if (response) + success = true; + } + } + return (success); +} + +//---------------------------------------- +// Set the minimum satellite signal level for navigation. +//---------------------------------------- +bool GNSS_ZED::setMinCnoRadio (uint8_t cnoValue) +{ + if (online.gnss) + { + _zed->setVal8(UBLOX_CFG_NAVSPG_INFIL_MINCNO, cnoValue); + return true; + } + return false; +} + +//---------------------------------------- +// Set the dynamic model to use for RTK +//---------------------------------------- +bool GNSS_ZED::setModel(uint8_t modelNumber) +{ + if (online.gnss) + { + _zed->setVal8(UBLOX_CFG_NAVSPG_DYNMODEL, (dynModel)modelNumber); // Set dynamic model + return true; + } + return false; +} + +//---------------------------------------- +bool GNSS_ZED::setRadioBaudRate(uint32_t baud) +{ + if (online.gnss) + return _zed->setVal32(UBLOX_CFG_UART2_BAUDRATE, baud); + return false; +} + +//---------------------------------------- +// Given the number of seconds between desired solution reports, determine measurementRateMs and navigationRate +// measurementRateS > 25 & <= 65535 +// navigationRate >= 1 && <= 127 +// We give preference to limiting a measurementRate to 30 or below due to reported problems with measRates above 30. +//---------------------------------------- +bool GNSS_ZED::setRate(double secondsBetweenSolutions) +{ + uint16_t measRate = 0; // Calculate these locally and then attempt to apply them to ZED at completion + uint16_t navRate = 0; + + if (online.gnss == false) + return (false); + + // If we have more than an hour between readings, increase mesaurementRate to near max of 65,535 + if (secondsBetweenSolutions > 3600.0) + { + measRate = 65000; + } + + // If we have more than 30s, but less than 3600s between readings, use 30s measurement rate + else if (secondsBetweenSolutions > 30.0) + { + measRate = 30000; + } + + // User wants measurements less than 30s (most common), set measRate to match user request + // This will make navRate = 1. + else + { + measRate = secondsBetweenSolutions * 1000.0; + } + + navRate = secondsBetweenSolutions * 1000.0 / measRate; // Set navRate to nearest int value + measRate = secondsBetweenSolutions * 1000.0 / navRate; // Adjust measurement rate to match actual navRate + + // systemPrintf("measurementRate / navRate: %d / %d\r\n", measRate, navRate); + + bool response = true; + response &= _zed->newCfgValset(); + response &= _zed->addCfgValset(UBLOX_CFG_RATE_MEAS, measRate); + response &= _zed->addCfgValset(UBLOX_CFG_RATE_NAV, navRate); + + int gsvRecordNumber = getMessageNumberByName("NMEA_GSV"); + + // If enabled, adjust GSV NMEA to be reported at 1Hz to avoid swamping SPP connection + if (settings.ubxMessageRates[gsvRecordNumber] > 0) + { + float measurementFrequency = (1000.0 / measRate) / navRate; + if (measurementFrequency < 1.0) + measurementFrequency = 1.0; + + log_d("Adjusting GSV setting to %f", measurementFrequency); + + setMessageRateByName("NMEA_GSV", measurementFrequency); // Update GSV setting in file + response &= _zed->addCfgValset(ubxMessages[gsvRecordNumber].msgConfigKey, + settings.ubxMessageRates[gsvRecordNumber]); // Update rate on module + } + + response &= _zed->sendCfgValset(); // Closing value - max 4 pairs + + // If we successfully set rates, only then record to settings + if (response) + { + settings.measurementRateMs = measRate; + settings.navigationRate = navRate; + } + else + { + systemPrintln("Failed to set measurement and navigation rates"); + return (false); + } + + return (true); +} + +//---------------------------------------- +bool GNSS_ZED::setTalkerGNGGA() +{ + if (online.gnss) + { + bool success = true; + success &= _zed->setVal8(UBLOX_CFG_NMEA_MAINTALKERID, 3); // Return talker ID to GNGGA after NTRIP Client set to GPGGA + success &= _zed->setNMEAGPGGAcallbackPtr(nullptr); // Remove callback + return success; + } + return false; +} + +//---------------------------------------- +// Hotstart GNSS to try to get RTK lock +//---------------------------------------- +bool GNSS_ZED::softwareReset() +{ + if (online.gnss == false) + return false; + _zed->softwareResetGNSSOnly(); + return true; +} + +//---------------------------------------- +bool GNSS_ZED::standby() +{ + return true; // TODO - this would be a perfect place for Save-On-Shutdown +} + +//---------------------------------------- +// Callback to save the high precision data +// Inputs: +// ubxDataStruct: Address of an UBX_NAV_HPPOSLLH_data_t structure +// containing the high precision position data +//---------------------------------------- +void storeHPdata(UBX_NAV_HPPOSLLH_data_t *ubxDataStruct) +{ + GNSS_ZED * zed = (GNSS_ZED *)gnss; + + zed->storeHPdataRadio(ubxDataStruct); +} + +//---------------------------------------- +// Callback to save the high precision data +//---------------------------------------- +void GNSS_ZED::storeHPdataRadio(UBX_NAV_HPPOSLLH_data_t *ubxDataStruct) +{ + double latitude; + double longitude; + + latitude = ((double)ubxDataStruct->lat) / 10000000.0; + latitude += ((double)ubxDataStruct->latHp) / 1000000000.0; + longitude = ((double)ubxDataStruct->lon) / 10000000.0; + longitude += ((double)ubxDataStruct->lonHp) / 1000000000.0; + + _horizontalAccuracy = ((float)ubxDataStruct->hAcc) / 10000.0; // Convert hAcc from mm*0.1 to m + _latitude = latitude; + _longitude = longitude; +} + +//---------------------------------------- +// Callback to save aStatus +//---------------------------------------- +void storeMONHWdata(UBX_MON_HW_data_t *ubxDataStruct) +{ + aStatus = ubxDataStruct->aStatus; +} + +//---------------------------------------- +// Callback to save the PVT data +// Inputs: +// ubxDataStruct: Address of an UBX_NAV_PVT_data_t structure +// containing the time, date and satellite data +//---------------------------------------- +void storePVTdata(UBX_NAV_PVT_data_t *ubxDataStruct) +{ + GNSS_ZED * zed = (GNSS_ZED *)gnss; + + zed->storePVTdataRadio(ubxDataStruct); +} + +//---------------------------------------- +// Callback to save the PVT data +//---------------------------------------- +void GNSS_ZED::storePVTdataRadio(UBX_NAV_PVT_data_t *ubxDataStruct) +{ + _altitude = ubxDataStruct->height / 1000.0; + + _day = ubxDataStruct->day; + _month = ubxDataStruct->month; + _year = ubxDataStruct->year; + + _hour = ubxDataStruct->hour; + _minute = ubxDataStruct->min; + _second = ubxDataStruct->sec; + _nanosecond = ubxDataStruct->nano; + _millisecond = ceil((ubxDataStruct->iTOW % 1000) / 10.0); // Limit to first two digits + + _satellitesInView = ubxDataStruct->numSV; + _fixType = ubxDataStruct->fixType; // 0 = no fix, 1 = dead reckoning only, 2 = 2D-fix, 3 = 3D-fix, 4 = GNSS + dead + // reckoning combined, 5 = time only fix + _carrierSolution = ubxDataStruct->flags.bits.carrSoln; + + _validDate = ubxDataStruct->valid.bits.validDate; + _validTime = ubxDataStruct->valid.bits.validTime; + _confirmedDate = ubxDataStruct->flags2.bits.confirmedDate; + _confirmedTime = ubxDataStruct->flags2.bits.confirmedTime; + _fullyResolved = ubxDataStruct->valid.bits.fullyResolved; + _tAcc = ubxDataStruct->tAcc; // Nanoseconds + + _pvtArrivalMillis = millis(); + _pvtUpdated = true; +} + +//---------------------------------------- +// Callback to save ARPECEF* +//---------------------------------------- +void storeRTCM1005data(RTCM_1005_data_t *rtcmData1005) +{ + ARPECEFX = rtcmData1005->AntennaReferencePointECEFX; + ARPECEFY = rtcmData1005->AntennaReferencePointECEFY; + ARPECEFZ = rtcmData1005->AntennaReferencePointECEFZ; + ARPECEFH = 0; + newARPAvailable = true; +} + +//---------------------------------------- +// Callback to save ARPECEF* +//---------------------------------------- +void storeRTCM1006data(RTCM_1006_data_t *rtcmData1006) +{ + ARPECEFX = rtcmData1006->AntennaReferencePointECEFX; + ARPECEFY = rtcmData1006->AntennaReferencePointECEFY; + ARPECEFZ = rtcmData1006->AntennaReferencePointECEFZ; + ARPECEFH = rtcmData1006->AntennaHeight; + newARPAvailable = true; +} + +//---------------------------------------- +void storeTIMTPdata(UBX_TIM_TP_data_t *ubxDataStruct) +{ + uint32_t tow = ubxDataStruct->week - SFE_UBLOX_JAN_1ST_2020_WEEK; // Calculate the number of weeks since Jan 1st + // 2020 + tow *= SFE_UBLOX_SECS_PER_WEEK; // Convert weeks to seconds + tow += SFE_UBLOX_EPOCH_WEEK_2086; // Add the TOW for Jan 1st 2020 + tow += ubxDataStruct->towMS / 1000; // Add the TOW for the next TP + + uint32_t us = ubxDataStruct->towMS % 1000; // Extract the milliseconds + us *= 1000; // Convert to microseconds + + double subMS = ubxDataStruct->towSubMS; // Get towSubMS (ms * 2^-32) + subMS *= pow(2.0, -32.0); // Convert to milliseconds + subMS *= 1000; // Convert to microseconds + + us += (uint32_t)subMS; // Add subMS + + timTpEpoch = tow; + timTpMicros = us; + timTpArrivalMillis = millis(); + timTpUpdated = true; +} + +//---------------------------------------- +// Slightly modified method for restarting survey-in from: +// https://portal.u-blox.com/s/question/0D52p00009IsVoMCAV/restarting-surveyin-on-an-f9p +//---------------------------------------- +bool GNSS_ZED::surveyInReset() +{ + bool response = true; + + if (online.gnss == false) + return (false); + + // Disable survey-in mode + response &= _zed->setVal8(UBLOX_CFG_TMODE_MODE, 0); + delay(1000); + + // Enable Survey in with bogus values + response &= _zed->newCfgValset(); + response &= _zed->addCfgValset(UBLOX_CFG_TMODE_MODE, 1); // Survey-in enable + response &= _zed->addCfgValset(UBLOX_CFG_TMODE_SVIN_ACC_LIMIT, 40 * 10000); // 40.0m + response &= _zed->addCfgValset(UBLOX_CFG_TMODE_SVIN_MIN_DUR, 1000); // 1000s + response &= _zed->sendCfgValset(); + delay(1000); + + // Disable survey-in mode + response &= _zed->setVal8(UBLOX_CFG_TMODE_MODE, 0); + + if (response == false) + return (response); + + // Wait until active and valid becomes false + long maxTime = 5000; + long startTime = millis(); + while (_zed->getSurveyInActive(100) || _zed->getSurveyInValid(100)) + { + delay(100); + if (millis() - startTime > maxTime) + return (false); // Reset of survey failed + } + + return (true); +} + +//---------------------------------------- +// Start the survey-in operation +// The ZED-F9P is slightly different than the NEO-M8P. See the Integration manual 3.5.8 for more info. +//---------------------------------------- +bool GNSS_ZED::surveyInStart() +{ + if (online.gnss == false) + return (false); + + _zed->setVal8(UBLOX_CFG_TMODE_MODE, 0); // Disable survey-in mode + delay(100); + + bool needSurveyReset = false; + if (_zed->getSurveyInActive(100)) + needSurveyReset = true; + if (_zed->getSurveyInValid(100)) + needSurveyReset = true; + + if (needSurveyReset) + { + systemPrintln("Resetting survey"); + + if (surveyInReset() == false) + { + systemPrintln("Survey reset failed - attempt 1/3"); + if (surveyInReset() == false) + { + systemPrintln("Survey reset failed - attempt 2/3"); + if (surveyInReset() == false) + { + systemPrintln("Survey reset failed - attempt 3/3"); + } + } + } + } + + bool response = true; + response &= _zed->setVal8(UBLOX_CFG_TMODE_MODE, 1); // Survey-in enable + response &= _zed->setVal32(UBLOX_CFG_TMODE_SVIN_ACC_LIMIT, settings.observationPositionAccuracy * 10000); + response &= _zed->setVal32(UBLOX_CFG_TMODE_SVIN_MIN_DUR, settings.observationSeconds); + + if (response == false) + { + systemPrintln("Survey start failed"); + return (false); + } + + systemPrintf("Survey started. This will run until %d seconds have passed and less than %0.03f meter accuracy is " + "achieved.\r\n", + settings.observationSeconds, settings.observationPositionAccuracy); + + // Wait until active becomes true + long maxTime = 5000; + long startTime = millis(); + while (_zed->getSurveyInActive(100) == false) + { + delay(100); + if (millis() - startTime > maxTime) + return (false); // Reset of survey failed + } + + return (true); +} + +//---------------------------------------- +int GNSS_ZED::ubxConstellationIDToIndex(int id) +{ + for (int x = 0; x < MAX_UBX_CONSTELLATIONS; x++) + { + if (settings.ubxConstellations[x].gnssID == id) + return x; + } + + return -1; // Should never be reached...! +} + +//---------------------------------------- +void GNSS_ZED::unlock(void) +{ + iAmLocked = false; +} + +//---------------------------------------- +// Poll routine to update the GNSS state +//---------------------------------------- +void GNSS_ZED::update() +{ + if (online.gnss) + { + _zed->checkUblox(); // Regularly poll to get latest data and any RTCM + _zed->checkCallbacks(); // Process any callbacks: ie, eventTriggerReceived + } +} + +//---------------------------------------- +void GNSS_ZED::updateCorrectionsSource(uint8_t source) +{ + if (!online.gnss) + return; + + if (zedCorrectionsSource == source) + return; + + // This is important. Retry if needed + int retries = 0; + while ((!_zed->setVal8(UBLOX_CFG_SPARTN_USE_SOURCE, source)) && (retries < 3)) + retries++; + if (retries < 3) + { + zedCorrectionsSource = source; + if (settings.debugCorrections && !inMainMenu) + systemPrintf("ZED UBLOX_CFG_SPARTN_USE_SOURCE changed to %d\r\n", source); + } + else + systemPrintf("updateZEDCorrectionsSource(%d) failed!\r\n", source); +} diff --git a/Firmware/RTK_Everywhere/LoRa.ino b/Firmware/RTK_Everywhere/LoRa.ino index 80434caa4..6e6e4ab34 100644 --- a/Firmware/RTK_Everywhere/LoRa.ino +++ b/Firmware/RTK_Everywhere/LoRa.ino @@ -125,7 +125,7 @@ void updateLora() case (LORA_TX_SETTLING): // While the survey is running, avoid transmitting over LoRa to allow maximum GNSS reception - if (gnssIsSurveyComplete() == true) + if (gnss->isSurveyInComplete() == true) { if (settings.debugLora == true) systemPrintln("LoRa: Moving to TX"); @@ -173,7 +173,7 @@ void updateLora() if (correctionLastSeen(CORR_RADIO_LORA)) { // Pass RTCM bytes (presumably) from LoRa out ESP32-UART to GNSS - gnssPushRawData(rtcmData, rtcmCount); // Push RTCM to GNSS module + gnss->pushRawData(rtcmData, rtcmCount); // Push RTCM to GNSS module if (((settings.debugCorrections == true) || (settings.debugLora == true)) && !inMainMenu) { @@ -252,7 +252,7 @@ void updateLora() if (correctionLastSeen(CORR_RADIO_LORA)) { // Pass RTCM bytes (presumably) from LoRa out ESP32-UART to GNSS - gnssPushRawData(rtcmData, rtcmCount); // Push RTCM to GNSS module + gnss->pushRawData(rtcmData, rtcmCount); // Push RTCM to GNSS module if (((settings.debugCorrections == true) || (settings.debugLora == true)) && !inMainMenu) { diff --git a/Firmware/RTK_Everywhere/MQTT_Client.ino b/Firmware/RTK_Everywhere/MQTT_Client.ino index e62a8a703..da11c26ba 100644 --- a/Firmware/RTK_Everywhere/MQTT_Client.ino +++ b/Firmware/RTK_Everywhere/MQTT_Client.ino @@ -368,8 +368,8 @@ void mqttClientReceiveMessage(int messageSize) float minDist = 99999.0; // Minimum distance to tile center in centidegrees char *preservedTile; char *tile = strtok_r(nodes, ",", &preservedTile); - int latitude = int(gnssGetLatitude() * 100.0); // Centidegrees - int longitude = int(gnssGetLongitude() * 100.0); // Centidegrees + int latitude = int(gnss->getLatitude() * 100.0); // Centidegrees + int longitude = int(gnss->getLongitude() * 100.0); // Centidegrees while (tile != nullptr) { char ns, ew; @@ -454,8 +454,8 @@ void mqttClientReceiveMessage(int messageSize) WeekToWToUnixEpoch(&settings.pointPerfectNextKeyStart, nextWeek, nextToW); settings.pointPerfectCurrentKeyStart -= - gnssGetLeapSeconds(); // Remove GPS leap seconds to align with u-blox - settings.pointPerfectNextKeyStart -= gnssGetLeapSeconds(); + gnss->getLeapSeconds(); // Remove GPS leap seconds to align with u-blox + settings.pointPerfectNextKeyStart -= gnss->getLeapSeconds(); settings.pointPerfectCurrentKeyStart *= 1000; // Convert to ms settings.pointPerfectNextKeyStart *= 1000; @@ -497,9 +497,10 @@ void mqttClientReceiveMessage(int messageSize) !inMainMenu) systemPrintf("Pushing %d bytes from %s topic to GNSS\r\n", mqttCount, topic); - updateZEDCorrectionsSource(0); // Set SOURCE to 0 (IP) if needed + GNSS_ZED * zed = (GNSS_ZED *)gnss; + zed->updateCorrectionsSource(0); // Set SOURCE to 0 (IP) if needed - gnssPushRawData(mqttData, mqttCount); + gnss->pushRawData(mqttData, mqttCount); bytesPushed += mqttCount; } else @@ -517,7 +518,7 @@ void mqttClientReceiveMessage(int messageSize) if (((settings.debugMqttClientData == true) || (settings.debugCorrections == true)) && !inMainMenu) systemPrintf("Pushing %d bytes from %s topic to GNSS\r\n", mqttCount, topic); - gnssPushRawData(mqttData, mqttCount); + gnss->pushRawData(mqttData, mqttCount); bytesPushed += mqttCount; } } @@ -952,9 +953,9 @@ void mqttClientUpdate() if ((strlen(settings.regionalCorrectionTopics[settings.geographicRegion]) > 0) && (settings.useLocalizedDistribution)) { - uint8_t fixType = gnssGetFixType(); - double latitude = gnssGetLatitude(); // degrees - double longitude = gnssGetLongitude(); // degrees + uint8_t fixType = gnss->getFixType(); + double latitude = gnss->getLatitude(); // degrees + double longitude = gnss->getLongitude(); // degrees if (fixType >= 3) // If we have a 3D fix { // If both the dict and tile topics are empty, prepare to subscribe to the dict topic diff --git a/Firmware/RTK_Everywhere/NTP.ino b/Firmware/RTK_Everywhere/NTP.ino index fc3b1540d..b45938c46 100644 --- a/Firmware/RTK_Everywhere/NTP.ino +++ b/Firmware/RTK_Everywhere/NTP.ino @@ -705,79 +705,10 @@ bool configureUbloxModuleNTP() log_d("Skipping ZED NTP configuration"); return (true); } - firstPowerOn = false; // If we switch between rover/base in the future, force config of module. - gnssUpdate(); // Regularly poll to get latest data - - theGNSS->setNMEAGPGGAcallbackPtr( - nullptr); // Disable GPGGA call back that may have been set during Rover NTRIP Client mode - - int tryNo = -1; - bool success = false; - - // Try up to MAX_SET_MESSAGES_RETRIES times to configure the GNSS - // This corrects occasional failures seen on the Reference Station where the GNSS is connected via SPI - // instead of I2C and UART1. I believe the SETVAL ACK is occasionally missed due to the level of messages being - // processed. - while ((++tryNo < MAX_SET_MESSAGES_RETRIES) && !success) - { - bool response = true; - - // In NTP mode we force 1Hz - response &= theGNSS->newCfgValset(); - response &= theGNSS->addCfgValset(UBLOX_CFG_RATE_MEAS, 1000); - response &= theGNSS->addCfgValset(UBLOX_CFG_RATE_NAV, 1); - - // Survey mode is only available on ZED-F9P modules - response &= theGNSS->addCfgValset(UBLOX_CFG_TMODE_MODE, 0); // Disable survey-in mode - - // Set dynamic model to stationary - response &= theGNSS->addCfgValset(UBLOX_CFG_NAVSPG_DYNMODEL, DYN_MODEL_STATIONARY); // Set dynamic model - - // Set time pulse to 1Hz (100:900) - response &= theGNSS->addCfgValset(UBLOX_CFG_TP_PULSE_DEF, 0); // Time pulse definition is a period (in us) - response &= theGNSS->addCfgValset(UBLOX_CFG_TP_PULSE_LENGTH_DEF, 1); // Define timepulse by length (not ratio) - response &= theGNSS->addCfgValset( - UBLOX_CFG_TP_USE_LOCKED_TP1, - 1); // Use CFG-TP-PERIOD_LOCK_TP1 and CFG-TP-LEN_LOCK_TP1 as soon as GNSS time is valid - response &= theGNSS->addCfgValset(UBLOX_CFG_TP_TP1_ENA, 1); // Enable timepulse - response &= theGNSS->addCfgValset(UBLOX_CFG_TP_POL_TP1, 1); // 1 = rising edge - - // While the module is _locking_ to GNSS time, turn off pulse - response &= theGNSS->addCfgValset(UBLOX_CFG_TP_PERIOD_TP1, 1000000); // Set the period between pulses in us - response &= theGNSS->addCfgValset(UBLOX_CFG_TP_LEN_TP1, 0); // Set the pulse length in us - - // When the module is _locked_ to GNSS time, make it generate 1Hz (100ms high, 900ms low) - response &= theGNSS->addCfgValset(UBLOX_CFG_TP_PERIOD_LOCK_TP1, 1000000); // Set the period between pulses is us - response &= theGNSS->addCfgValset(UBLOX_CFG_TP_LEN_LOCK_TP1, 100000); // Set the pulse length in us - - // Ensure pulse is aligned to top-of-second. This is the default. Set it here just to make sure. - response &= theGNSS->addCfgValset(UBLOX_CFG_TP_ALIGN_TO_TOW_TP1, 1); - - // Set the time grid to UTC. This is the default. Set it here just to make sure. - response &= theGNSS->addCfgValset(UBLOX_CFG_TP_TIMEGRID_TP1, 0); // 0=UTC; 1=GPS - - // Sync to GNSS. This is the default. Set it here just to make sure. - response &= theGNSS->addCfgValset(UBLOX_CFG_TP_SYNC_GNSS_TP1, 1); - - response &= theGNSS->addCfgValset(UBLOX_CFG_NAVSPG_INFIL_MINELEV, settings.minElev); // Set minimum elevation - - // Ensure PVT, HPPOSLLH and TP messages are being output at 1Hz on the correct port - response &= theGNSS->addCfgValset(UBLOX_CFG_MSGOUT_UBX_NAV_PVT_I2C, 1); - response &= theGNSS->addCfgValset(UBLOX_CFG_MSGOUT_UBX_NAV_HPPOSLLH_I2C, 1); - response &= theGNSS->addCfgValset(UBLOX_CFG_MSGOUT_UBX_TIM_TP_I2C, 1); - - response &= theGNSS->sendCfgValset(); // Closing value - - if (response) - success = true; - } - - if (!success) - systemPrintln("NTP config fail"); - - return (success); + gnss->update(); // Regularly poll to get latest data + return gnss->configureNtpMode(); } //=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= diff --git a/Firmware/RTK_Everywhere/NVM.ino b/Firmware/RTK_Everywhere/NVM.ino index 00ac54b5f..b64a51ed9 100644 --- a/Firmware/RTK_Everywhere/NVM.ino +++ b/Firmware/RTK_Everywhere/NVM.ino @@ -369,7 +369,8 @@ void recordSystemSettingsToFile(File *settingsFile) case tUbMsgRtb: { // Record message settings - int firstRTCMRecord = zedGetMessageNumberByName("RTCM_1005"); + GNSS_ZED * zed = (GNSS_ZED *)gnss; + int firstRTCMRecord = zed->getMessageNumberByName("RTCM_1005"); for (int x = 0; x < rtkSettingsEntries[i].qualifier; x++) { @@ -1124,7 +1125,8 @@ bool parseLine(char *str) } break; case tUbMsgRtb: { - int firstRTCMRecord = zedGetMessageNumberByName("RTCM_1005"); + GNSS_ZED * zed = (GNSS_ZED *)gnss; + int firstRTCMRecord = zed->getMessageNumberByName("RTCM_1005"); for (int x = 0; x < qualifier; x++) { diff --git a/Firmware/RTK_Everywhere/NtripClient.ino b/Firmware/RTK_Everywhere/NtripClient.ino index 9a3717825..2c01c87db 100644 --- a/Firmware/RTK_Everywhere/NtripClient.ino +++ b/Firmware/RTK_Everywhere/NtripClient.ino @@ -511,7 +511,7 @@ void ntripClientStop(bool shutdown) ntripClientTimer = millis(); // Return the Main Talker ID to "GN". - gnssSetTalkerGNGGA(); + gnss->setTalkerGNGGA(); // Determine the next NTRIP client state online.ntripClient = false; @@ -594,7 +594,7 @@ void ntripClientUpdate() // If GGA transmission is enabled, wait for GNSS lock before connecting to NTRIP Caster // If GGA transmission is not enabled, start connecting to NTRIP Caster - else if ((settings.ntripClient_TransmitGGA == false) || (gnssIsFixed() == true)) + else if ((settings.ntripClient_TransmitGGA == false) || (gnss->isFixed() == true)) { // Delay before opening the NTRIP client connection if ((millis() - ntripClientTimer) >= ntripClientConnectionAttemptTimeout) @@ -707,7 +707,7 @@ void ntripClientUpdate() { // Set the Main Talker ID to "GP". The NMEA GGA messages will be GPGGA instead of GNGGA // Tell the module to output GGA every 5 seconds - gnssEnableGgaForNtrip(); + gnss->enableGgaForNtrip(); lastGGAPush = millis() - NTRIPCLIENT_MS_BETWEEN_GGA; // Force immediate transmission of GGA message @@ -821,7 +821,7 @@ void ntripClientUpdate() if (correctionLastSeen(CORR_TCP)) { // Push RTCM to GNSS module over I2C / SPI - gnssPushRawData(rtcmData, rtcmCount); + gnss->pushRawData(rtcmData, rtcmCount); if ((settings.debugCorrections || settings.debugNtripClientRtcm || PERIODIC_DISPLAY(PD_NTRIP_CLIENT_DATA)) && (!inMainMenu)) diff --git a/Firmware/RTK_Everywhere/PointPerfectLibrary.ino b/Firmware/RTK_Everywhere/PointPerfectLibrary.ino index 5eefc8ed5..91089227b 100644 --- a/Firmware/RTK_Everywhere/PointPerfectLibrary.ino +++ b/Firmware/RTK_Everywhere/PointPerfectLibrary.ino @@ -42,9 +42,10 @@ void updatePplTask(void *e) // Note: this is almost certainly redundant. It would only be used if we // believe the PPL can do a better job generating corrections than the // ZED can internally using SPARTN direct. - updateZEDCorrectionsSource(1); + GNSS_ZED * zed = (GNSS_ZED *)gnss; + zed->updateCorrectionsSource(1); - gnssPushRawData(pplRtcmBuffer, rtcmLength); + gnss->pushRawData(pplRtcmBuffer, rtcmLength); if (settings.debugCorrections == true && !inMainMenu) systemPrintf("Received %d RTCM bytes from PPL. Pushed to the GNSS.\r\n", rtcmLength); @@ -233,7 +234,7 @@ void updatePPL() static unsigned long pplTime3dFixStarted; - if ((online.ppl == false) && (settings.enablePointPerfectCorrections) && (gnssIsFixed())) + if ((online.ppl == false) && (settings.enablePointPerfectCorrections) && (gnss->isFixed())) { // Start PPL only after GNSS is outputting appropriate NMEA+RTCM, we have a key, and the MQTT broker is // connected or L-Band SPARTN is being received. Don't restart the PPL if we've already tried @@ -256,7 +257,7 @@ void updatePPL() { pplReport = millis(); - if (gnssIsRTKFloat() && pplTimeFloatStarted > 0) + if (gnss->isRTKFloat() && pplTimeFloatStarted > 0) { systemPrintf("GNSS restarts: %d Time remaining before Float lock forced restart: %ds\r\n", floatLockRestarts, @@ -278,7 +279,7 @@ void updatePPL() } } - if (gnssIsRTKFloat()) + if (gnss->isRTKFloat()) { if (pplTimeFloatStarted == 0) pplTimeFloatStarted = millis(); @@ -300,7 +301,7 @@ void updatePPL() } } } - else if (gnssIsRTKFix()) + else if (gnss->isRTKFix()) { if (pplTimeFloatStarted != 0) pplTimeFloatStarted = 0; // Reset pplTimeFloatStarted @@ -320,7 +321,7 @@ void updatePPL() { // We are not in RTK Float or RTK Fix - if (gnssIsFixed() == false) + if (gnss->isFixed() == false) pplTimeFloatStarted = 0; // Reset pplTimeFloatStarted if we loose a 3D fix entirely } } diff --git a/Firmware/RTK_Everywhere/RTK_Everywhere.ino b/Firmware/RTK_Everywhere/RTK_Everywhere.ino index aa3e829e6..627c7fe73 100644 --- a/Firmware/RTK_Everywhere/RTK_Everywhere.ino +++ b/Firmware/RTK_Everywhere/RTK_Everywhere.ino @@ -338,65 +338,11 @@ int wifiOriginalMaxConnectionAttempts = wifiMaxConnectionAttempts; // Modified d // GNSS configuration - ZED-F9x //=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= #include //http://librarymanager/All#SparkFun_u-blox_GNSS_v3 +#include "GNSS_ZED.h" -char neoFirmwareVersion[20]; // Output to system status menu. - -// Use Michael's lock/unlock methods to prevent the GNSS UART task from calling checkUblox during a sendCommand and -// waitForResponse. Also prevents pushRawData from being called. -class SFE_UBLOX_GNSS_SUPER_DERIVED : public SFE_UBLOX_GNSS_SUPER -{ - public: - // SemaphoreHandle_t gnssSemaphore = nullptr; - - // Revert to a simple bool lock. The Mutex was causing occasional panics caused by - // vTaskPriorityDisinheritAfterTimeout in lock() (I think possibly / probably caused by the GNSS not being pinned to - // one core? - bool iAmLocked = false; - - bool createLock(void) - { - // if (gnssSemaphore == nullptr) - // gnssSemaphore = xSemaphoreCreateMutex(); - // return gnssSemaphore; - - return true; - } - bool lock(void) - { - // return (xSemaphoreTake(gnssSemaphore, 2100) == pdPASS); - - if (!iAmLocked) - { - iAmLocked = true; - return true; - } - - unsigned long startTime = millis(); - while (((millis() - startTime) < 2100) && (iAmLocked)) - delay(1); // Yield +GNSS * gnss; - if (!iAmLocked) - { - iAmLocked = true; - return true; - } - - return false; - } - void unlock(void) - { - // xSemaphoreGive(gnssSemaphore); - - iAmLocked = false; - } - void deleteLock(void) - { - // vSemaphoreDelete(gnssSemaphore); - // gnssSemaphore = nullptr; - } -}; - -SFE_UBLOX_GNSS_SUPER_DERIVED *theGNSS = nullptr; // Don't instantiate until we know what gnssPlatform we're on +char neoFirmwareVersion[20]; // Output to system status menu. #ifdef COMPILE_L_BAND static SFE_UBLOX_GNSS_SUPER i2cLBand; // NEO-D9S @@ -430,12 +376,8 @@ bool usbSerialIncomingRtcm; // Incoming RTCM over the USB serial port #define RTCM_CORRECTION_INPUT_TIMEOUT (2 * 1000) //=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= -// GNSS configuration - UM980 +// Extensible Message Parser //=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= -#ifdef COMPILE_UM980 -#include //http://librarymanager/All#SparkFun_Unicore_GNSS -#endif // COMPILE_UM980 - #include //http://librarymanager/All#SparkFun_Extensible_Message_Parser SEMP_PARSE_STATE *rtkParse = nullptr; SEMP_PARSE_STATE *sbfParse = nullptr; // mosaic-X5 @@ -779,7 +721,6 @@ unsigned long startTime; // Used for checking longest-running functi bool lbandCorrectionsReceived; // Used to display L-Band SIV icon when corrections are successfully decrypted (NEO-D9S only) unsigned long lastLBandDecryption; // Timestamp of last successfully decrypted PMP message from NEO-D9S volatile bool mqttMessageReceived; // Goes true when the subscribed MQTT channel reports back -uint8_t leapSeconds; // Gets set if GNSS is online unsigned long systemTestDisplayTime; // Timestamp for swapping the graphic during testing uint8_t systemTestDisplayNumber; // Tracks which test screen we're looking at unsigned long rtcWaitTime; // At power on, we give the RTC a few seconds to update during PointPerfect Key checking @@ -821,8 +762,6 @@ volatile PeriodicDisplay_t periodicDisplay; unsigned long shutdownNoChargeTimer; -unsigned long autoBaseStartTimer; // Tracks how long the base auto / averaging mode has been running - RtkMode_t rtkMode; // Mode of operation unsigned long beepLengthMs; // Number of ms to make noise @@ -1064,9 +1003,9 @@ void setup() if (checkUpdateLoraFirmware() == true) // Check if updateLoraFirmware.txt exists beginLoraFirmwareUpdate(); - DMW_b("checkUpdateUm980Firmware"); - if (checkUpdateUm980Firmware() == true) // Check if updateLoraFirmware.txt exists - beginUm980FirmwareUpdate(); + DMW_b("um980FirmwareCheckUpdate"); + if (um980FirmwareCheckUpdate() == true) // Check if updateUm980Firmware.txt exists + um980FirmwareBeginUpdate(); DMW_b("checkConfigureViaEthernet"); configureViaEthernet = @@ -1100,8 +1039,8 @@ void setup() DMW_b("displaySplash"); displaySplash(); // Display the RTK product name and firmware version - DMW_b("gnssBegin"); - gnssBegin(); // Requires settings. Connect to GNSS to get module type + DMW_b("gnss->begin"); + gnss->begin(); // Requires settings. Connect to GNSS to get module type DMW_b("beginSD"); beginSD(); // Requires settings. Test if SD is present @@ -1129,17 +1068,17 @@ void setup() DMW_b("beginCharger"); beginCharger(); // Configure battery charger - DMW_b("gnssConfigure"); - gnssConfigure(); // Requires settings. Configure GNSS module + DMW_b("gnss->configure"); + gnss->configure(); // Requires settings. Configure GNSS module DMW_b("beginLBand"); beginLBand(); // Begin L-Band DMW_b("beginExternalEvent"); - gnssBeginExternalEvent(); // Configure the event input + gnss->beginExternalEvent(); // Configure the event input DMW_b("beginPPS"); - gnssBeginPPS(); // Configure the time pulse output + gnss->beginPPS(); // Configure the time pulse output DMW_b("beginInterrupts"); beginInterrupts(); // Begin the TP interrupts @@ -1228,8 +1167,8 @@ void loop() DMW_c("periodicDisplay"); updatePeriodicDisplay(); - DMW_c("gnssUpdate"); - gnssUpdate(); + DMW_c("gnss->update"); + gnss->update(); DMW_c("stateUpdate"); stateUpdate(); @@ -1467,21 +1406,21 @@ void rtcUpdate() { lastRTCAttempt = millis(); - // gnssUpdate() is called in loop() but rtcUpdate + // gnss->update() is called in loop() but rtcUpdate // can also be called during begin. To be safe, check for fresh PVT data here. - gnssUpdate(); + gnss->update(); bool timeValid = false; - if (gnssIsValidTime() == true && - gnssIsValidDate() == true) // Will pass if ZED's RTC is reporting (regardless of GNSS fix) + if (gnss->isValidTime() == true && + gnss->isValidDate() == true) // Will pass if ZED's RTC is reporting (regardless of GNSS fix) timeValid = true; - if (gnssIsConfirmedTime() == true && gnssIsConfirmedDate() == true) // Requires GNSS fix + if (gnss->isConfirmedTime() == true && gnss->isConfirmedDate() == true) // Requires GNSS fix timeValid = true; if (timeValid && - (gnssGetFixAgeMilliseconds() > 999)) // If the GNSS time is over a second old, don't use it + (gnss->getFixAgeMilliseconds() > 999)) // If the GNSS time is over a second old, don't use it timeValid = false; if (timeValid == true) diff --git a/Firmware/RTK_Everywhere/States.ino b/Firmware/RTK_Everywhere/States.ino index e7a6fa4c6..5d0d315d8 100644 --- a/Firmware/RTK_Everywhere/States.ino +++ b/Firmware/RTK_Everywhere/States.ino @@ -98,7 +98,7 @@ void stateUpdate() // Configure for rover mode displayRoverStart(0); - if (gnssConfigureRover() == false) + if (gnss->configureRover() == false) { systemPrintln("Rover config failed"); displayRoverFail(1000); @@ -129,31 +129,31 @@ void stateUpdate() break; case (STATE_ROVER_NO_FIX): { - if (gnssIsFixed()) // 3D, 3D+DR + if (gnss->isFixed()) // 3D, 3D+DR changeState(STATE_ROVER_FIX); } break; case (STATE_ROVER_FIX): { - if (gnssIsRTKFloat()) + if (gnss->isRTKFloat()) changeState(STATE_ROVER_RTK_FLOAT); - else if (gnssIsRTKFix()) + else if (gnss->isRTKFix()) changeState(STATE_ROVER_RTK_FIX); } break; case (STATE_ROVER_RTK_FLOAT): { - if (gnssIsRTKFix() == false && gnssIsRTKFloat() == false) // No RTK + if (gnss->isRTKFix() == false && gnss->isRTKFloat() == false) // No RTK changeState(STATE_ROVER_FIX); - if (gnssIsRTKFix() == true) + if (gnss->isRTKFix() == true) changeState(STATE_ROVER_RTK_FIX); } break; case (STATE_ROVER_RTK_FIX): { - if (gnssIsRTKFix() == false && gnssIsRTKFloat() == false) // No RTK + if (gnss->isRTKFix() == false && gnss->isRTKFloat() == false) // No RTK changeState(STATE_ROVER_FIX); - if (gnssIsRTKFloat()) + if (gnss->isRTKFloat()) changeState(STATE_ROVER_RTK_FLOAT); } break; @@ -215,7 +215,7 @@ void stateUpdate() bluetoothStart(); // Restart Bluetooth with 'Base' identifier // Start the UART connected to the GNSS receiver for NMEA and UBX data (enables logging) - if (tasksStartGnssUart() && gnssConfigureBase()) + if (tasksStartGnssUart() && gnss->configureBase()) { settings.updateGNSSSettings = false; // On the next boot, no need to update the GNSS on this profile settings.lastState = STATE_BASE_NOT_STARTED; // Record this state for next POR @@ -245,26 +245,26 @@ void stateUpdate() baseStatusLedBlink(); // Toggle the base/status LED } - int siv = gnssGetSatellitesInView(); - float hpa = gnssGetHorizontalAccuracy(); + int siv = gnss->getSatellitesInView(); + float hpa = gnss->getHorizontalAccuracy(); // Check for <1m horz accuracy before starting surveyIn char accuracy[20]; char temp[20]; const char *units = getHpaUnits(hpa, temp, sizeof(temp), 2, true); // gnssGetSurveyInStartingAccuracy is 10m max - const char *accUnits = getHpaUnits(gnssGetSurveyInStartingAccuracy(), accuracy, sizeof(accuracy), 2, false); + const char *accUnits = getHpaUnits(gnss->getSurveyInStartingAccuracy(), accuracy, sizeof(accuracy), 2, false); systemPrintf("Waiting for Horz Accuracy < %s (%s): %s%s%s%s, SIV: %d\r\n", accuracy, accUnits, temp, (accUnits != units) ? " (" : "", (accUnits != units) ? units : "", (accUnits != units) ? ")" : "", siv); // On the mosaic-X5, the HPA is undefined while the GNSS is determining its fixed position // We need to skip the HPA check... - if ((hpa > 0.0 && hpa < gnssGetSurveyInStartingAccuracy()) || present.gnss_mosaicX5) + if ((hpa > 0.0 && hpa < gnss->getSurveyInStartingAccuracy()) || present.gnss_mosaicX5) { displaySurveyStart(0); // Show 'Survey' - if (gnssSurveyInStart() == true) // Begin survey + if (gnss->surveyInStart() == true) // Begin survey { displaySurveyStarted(500); // Show 'Survey Started' @@ -285,11 +285,11 @@ void stateUpdate() } // Get the data once to avoid duplicate slow responses - int observationTime = gnssGetSurveyInObservationTime(); - float meanAccuracy = gnssGetSurveyInMeanAccuracy(); - int siv = gnssGetSatellitesInView(); + int observationTime = gnss->getSurveyInObservationTime(); + float meanAccuracy = gnss->getSurveyInMeanAccuracy(); + int siv = gnss->getSatellitesInView(); - if (gnssIsSurveyComplete() == true) // Survey in complete + if (gnss->isSurveyInComplete() == true) // Survey in complete { systemPrintf("Observation Time: %d\r\n", observationTime); systemPrintln("Base survey complete! RTCM now broadcasting."); @@ -315,13 +315,13 @@ void stateUpdate() systemPrintf("Survey-In took more than %d minutes. Returning to rover mode.\r\n", maxSurveyInWait_s / 60); - if (gnssSurveyInReset() == false) + if (gnss->surveyInReset() == false) { systemPrintln("Survey reset failed - attempt 1/3"); - if (gnssSurveyInReset() == false) + if (gnss->surveyInReset() == false) { systemPrintln("Survey reset failed - attempt 2/3"); - if (gnssSurveyInReset() == false) + if (gnss->surveyInReset() == false) { systemPrintln("Survey reset failed - attempt 3/3"); } @@ -361,7 +361,7 @@ void stateUpdate() // If fixed base fails, we'll handle it here case (STATE_BASE_FIXED_NOT_STARTED): { RTK_MODE(RTK_MODE_BASE_FIXED); - bool response = gnssFixedBaseStart(); + bool response = gnss->fixedBaseStart(); if (response == true) { baseStatusLedOn(); // Turn on the base/status LED @@ -476,7 +476,7 @@ void stateUpdate() tasksStopGnssUart(); // Stop absoring GNSS serial via task zedUartPassed = false; - gnssEnableRTCMTest(); + gnss->enableRTCMTest(); RTK_MODE(RTK_MODE_TESTING); changeState(STATE_TESTING); diff --git a/Firmware/RTK_Everywhere/System.ino b/Firmware/RTK_Everywhere/System.ino index d1d807813..d854bb2d1 100644 --- a/Firmware/RTK_Everywhere/System.ino +++ b/Firmware/RTK_Everywhere/System.ino @@ -354,31 +354,31 @@ void printReports() lastPrintRoverAccuracy = millis(); PERIODIC_CLEAR(PD_MQTT_CLIENT_DATA); - if (online.gnss == true) + if (online.gnss) { // If we are in rover mode, display HPA and SIV if (inRoverMode() == true) { - float hpa = gnssGetHorizontalAccuracy(); + float hpa = gnss->getHorizontalAccuracy(); char modifiedHpa[20]; const char *hpaUnits = getHpaUnits(hpa, modifiedHpa, sizeof(modifiedHpa), 3, true); // Returns string of the HPA units systemPrintf("Rover Accuracy (%s): %s, SIV: %d GNSS State: ", hpaUnits, modifiedHpa, - gnssGetSatellitesInView()); + gnss->getSatellitesInView()); - if (gnssIsRTKFix() == true) + if (gnss->isRTKFix() == true) systemPrint("RTK Fix"); - else if (gnssIsRTKFloat() == true) + else if (gnss->isRTKFloat() == true) systemPrint("RTK Float"); - else if (gnssIsPppConverged() == true) + else if (gnss->isPppConverged() == true) systemPrint("PPP Converged"); - else if (gnssIsPppConverging() == true) + else if (gnss->isPppConverging() == true) systemPrint("PPP Converging"); - else if (gnssIsDgpsFixed() == true) + else if (gnss->isDgpsFixed() == true) systemPrint("DGPS Fix"); - else if (gnssIsFixed() == true) + else if (gnss->isFixed() == true) systemPrint("3D Fix"); else systemPrint("No Fix"); @@ -389,7 +389,7 @@ void printReports() // If we are in base mode, display SIV only else if (inBaseMode() == true) { - systemPrintf("Base Mode - SIV: %d\r\n", gnssGetSatellitesInView()); + systemPrintf("Base Mode - SIV: %d\r\n", gnss->getSatellitesInView()); } } } @@ -759,18 +759,18 @@ const char *getHpaUnits(double hpa, char *buffer, int length, int decimals, bool void convertGnssTimeToEpoch(uint32_t *epochSecs, uint32_t *epochMicros) { uint32_t t = SFE_UBLOX_DAYS_FROM_1970_TO_2020; // Jan 1st 2020 as days from Jan 1st 1970 - t += (uint32_t)SFE_UBLOX_DAYS_SINCE_2020[gnssGetYear() - 2020]; // Add on the number of days since 2020 - t += (uint32_t)SFE_UBLOX_DAYS_SINCE_MONTH[gnssGetYear() % 4 == 0 ? 0 : 1] - [gnssGetMonth() - 1]; // Add on the number of days since Jan 1st - t += (uint32_t)gnssGetDay() - 1; // Add on the number of days since the 1st of the month - t *= 24; // Convert to hours - t += (uint32_t)gnssGetHour(); // Add on the hour - t *= 60; // Convert to minutes - t += (uint32_t)gnssGetMinute(); // Add on the minute - t *= 60; // Convert to seconds - t += (uint32_t)gnssGetSecond(); // Add on the second - - int32_t us = gnssGetNanosecond() / 1000; // Convert nanos to micros + t += (uint32_t)SFE_UBLOX_DAYS_SINCE_2020[gnss->getYear() - 2020]; // Add on the number of days since 2020 + t += (uint32_t)SFE_UBLOX_DAYS_SINCE_MONTH[gnss->getYear() % 4 == 0 ? 0 : 1] + [gnss->getMonth() - 1]; // Add on the number of days since Jan 1st + t += (uint32_t)gnss->getDay() - 1; // Add on the number of days since the 1st of the month + t *= 24; // Convert to hours + t += (uint32_t)gnss->getHour(); // Add on the hour + t *= 60; // Convert to minutes + t += (uint32_t)gnss->getMinute(); // Add on the minute + t *= 60; // Convert to seconds + t += (uint32_t)gnss->getSecond(); // Add on the second + + int32_t us = gnss->getNanosecond() / 1000; // Convert nanos to micros uint32_t micro; // Adjust t if nano is negative if (us < 0) diff --git a/Firmware/RTK_Everywhere/Tasks.ino b/Firmware/RTK_Everywhere/Tasks.ino index a8ac2eecd..e32fd6990 100644 --- a/Firmware/RTK_Everywhere/Tasks.ino +++ b/Firmware/RTK_Everywhere/Tasks.ino @@ -291,7 +291,7 @@ void sendGnssBuffer() { if (correctionLastSeen(CORR_BLUETOOTH)) { - if (gnssPushRawData(bluetoothOutgoingToGnss, bluetoothOutgoingToGnssHead)) + if (gnss->pushRawData(bluetoothOutgoingToGnss, bluetoothOutgoingToGnssHead)) { if ((settings.debugCorrections || PERIODIC_DISPLAY(PD_ZED_DATA_TX)) && !inMainMenu) { @@ -450,7 +450,7 @@ void gnssReadTask(void *e) // to add extra checks, above and beyond the invalidDataCallback, to make sure that doesn't happen. // Here we check that the SBF ID and length are expected / valid too. - if (gnssIsBlocking() == false) + if (gnss->isBlocking() == false) { // Determine if serial data is available while (serialGNSS->available()) @@ -1372,7 +1372,7 @@ void tickerGnssLedUpdate() // Update the GNSS LED according to our state // Solid once RTK Fix is achieved, or PPP converges - if (gnssIsRTKFix() == true || gnssIsPppConverged()) + if (gnss->isRTKFix() == true || gnss->isPppConverged()) { ledcWrite(pin_gnssStatusLED, 255); } diff --git a/Firmware/RTK_Everywhere/UM980.h b/Firmware/RTK_Everywhere/UM980.h deleted file mode 100644 index 54b375ca1..000000000 --- a/Firmware/RTK_Everywhere/UM980.h +++ /dev/null @@ -1,94 +0,0 @@ -#ifndef _RTK_EVERYWHERE_UM980_H -#define _RTK_EVERYWHERE_UM980_H - -/* - Unicore defaults: - RTCM1006 10 - RTCM1074 1 - RTCM1084 1 - RTCM1094 1 - RTCM1124 1 - RTCM1033 10 -*/ - -// Each constellation will have its config command text, enable, and a visible name -typedef struct -{ - char textName[30]; - char textCommand[5]; -} um980ConstellationCommand; - -// Constellations monitored/used for fix -// Available constellations: GPS, BDS, GLO, GAL, QZSS -// SBAS and IRNSS don't seem to be supported -const um980ConstellationCommand um980ConstellationCommands[] = { - {"BeiDou", "BDS"}, - {"Galileo", "GAL"}, - {"GLONASS", "GLO"}, - {"GPS", "GPS"}, - {"QZSS", "QZSS"}, -}; - -#define MAX_UM980_CONSTELLATIONS (sizeof(um980ConstellationCommands) / sizeof(um980ConstellationCommand)) - -// Struct to describe support messages on the UM980 -// Each message will have the serial command and its default value -typedef struct -{ - const char msgTextName[9]; - const float msgDefaultRate; -} um980Msg; - -// Static array containing all the compatible messages -const um980Msg umMessagesNMEA[] = { - // NMEA - {"GPDTM", 0}, {"GPGBS", 0}, {"GPGGA", 0.5}, {"GPGLL", 0}, {"GPGNS", 0}, - - {"GPGRS", 0}, {"GPGSA", 0.5}, {"GPGST", 0.5}, {"GPGSV", 1}, {"GPRMC", 0.5}, - - {"GPROT", 0}, {"GPTHS", 0}, {"GPVTG", 0}, {"GPZDA", 0}, -}; - -const um980Msg umMessagesRTCM[] = { - - // RTCM - {"RTCM1001", 0}, {"RTCM1002", 0}, {"RTCM1003", 0}, {"RTCM1004", 0}, {"RTCM1005", 1}, - {"RTCM1006", 0}, {"RTCM1007", 0}, {"RTCM1009", 0}, {"RTCM1010", 0}, - - {"RTCM1011", 0}, {"RTCM1012", 0}, {"RTCM1013", 0}, {"RTCM1019", 0}, - - {"RTCM1020", 0}, - - {"RTCM1033", 10}, - - {"RTCM1042", 0}, {"RTCM1044", 0}, {"RTCM1045", 0}, {"RTCM1046", 0}, - - {"RTCM1071", 0}, {"RTCM1072", 0}, {"RTCM1073", 0}, {"RTCM1074", 1}, {"RTCM1075", 0}, - {"RTCM1076", 0}, {"RTCM1077", 0}, - - {"RTCM1081", 0}, {"RTCM1082", 0}, {"RTCM1083", 0}, {"RTCM1084", 1}, {"RTCM1085", 0}, - {"RTCM1086", 0}, {"RTCM1087", 0}, - - {"RTCM1091", 0}, {"RTCM1092", 0}, {"RTCM1093", 0}, {"RTCM1094", 1}, {"RTCM1095", 0}, - {"RTCM1096", 0}, {"RTCM1097", 0}, - - {"RTCM1104", 0}, - - {"RTCM1111", 0}, {"RTCM1112", 0}, {"RTCM1113", 0}, {"RTCM1114", 0}, {"RTCM1115", 0}, - {"RTCM1116", 0}, {"RTCM1117", 0}, - - {"RTCM1121", 0}, {"RTCM1122", 0}, {"RTCM1123", 0}, {"RTCM1124", 1}, {"RTCM1125", 0}, - {"RTCM1126", 0}, {"RTCM1127", 0}, -}; - -#define MAX_UM980_NMEA_MSG (sizeof(umMessagesNMEA) / sizeof(um980Msg)) -#define MAX_UM980_RTCM_MSG (sizeof(umMessagesRTCM) / sizeof(um980Msg)) - -enum um980_Models -{ - UM980_DYN_MODEL_SURVEY = 0, - UM980_DYN_MODEL_UAV, - UM980_DYN_MODEL_AUTOMOTIVE, -}; - -#endif diff --git a/Firmware/RTK_Everywhere/UM980.ino b/Firmware/RTK_Everywhere/UM980.ino deleted file mode 100644 index 2bcf8f532..000000000 --- a/Firmware/RTK_Everywhere/UM980.ino +++ /dev/null @@ -1,1414 +0,0 @@ -/* - IM19 reads in binary+NMEA from the UM980 and passes out binary with tilt-corrected lat/long/alt - to the ESP32. - - The ESP32 reads in binary from the IM19. - - The ESP32 reads in binary and NMEA from the UM980 and passes that data over Bluetooth. - If tilt compensation is activated, the ESP32 intercepts the NMEA from the UM980 and - injects the new tilt-compensated data, previously read from the IM19. -*/ - -#ifdef COMPILE_UM980 - -UM980 *um980 = nullptr; // Don't instantiate until we know what gnssPlatform we're on - -void um980Begin() -{ - // During identifyBoard(), the GNSS UART and DR pins are set - - // The GNSS UART is already started. We can now pass it to the library. - if (serialGNSS == nullptr) - { - systemPrintln("GNSS UART not started"); - return; - } - - // Instantiate the library - um980 = new UM980(); - - // Turn on/off debug messages - if (settings.debugGnss == true) - um980EnableDebugging(); - - // In order to reduce UM980 configuration time, the UM980 library blocks the start of BESTNAV and RECTIME until 3D - // fix is achieved However, if all NMEA messages are disabled, the UM980 will never detect a 3D fix. - if (um980IsGgaActive() == true) - // If NMEA GPGGA is turned on, suppress BESTNAV messages until GPGGA reports a 3D fix - um980->disableBinaryBeforeFix(); - else - // If NMEA GPGGA is turned off, enable BESTNAV messages at power on which may lead to longer UM980 configuration - // times - um980->enableBinaryBeforeFix(); - - if (um980->begin(*serialGNSS) == false) // Give the serial port over to the library - { - if (settings.debugGnss) - systemPrintln("GNSS Failed to begin. Trying again."); - - // Try again with power on delay - delay(1000); - if (um980->begin(*serialGNSS) == false) - { - systemPrintln("GNSS offline"); - displayGNSSFail(1000); - return; - } - } - systemPrintln("GNSS UM980 online"); - - // Shortly after reset, the UM980 responds to the VERSIONB command with OK but doesn't report version information - if (ENABLE_DEVELOPER == false) - delay(2000); // 1s fails, 2s ok - - // Check firmware version and print info - um980PrintInfo(); - - // Shortly after reset, the UM980 responds to the VERSIONB command with OK but doesn't report version information - snprintf(gnssFirmwareVersion, sizeof(gnssFirmwareVersion), "%s", um980->getVersion()); - - // getVersion returns the "Build" "7923". I think we probably need the "R4.10" which preceeds Build? TODO - if (sscanf(gnssFirmwareVersion, "%d", &gnssFirmwareVersionInt) != 1) - gnssFirmwareVersionInt = 99; - - snprintf(gnssUniqueId, sizeof(gnssUniqueId), "%s", um980->getID()); - - online.gnss = true; -} - -bool um980IsBlocking() -{ - return um980->isBlocking(); -} - -// Attempt 3 tries on UM980 config -bool um980Configure() -{ - // Skip configuring the UM980 if no new changes are necessary - if (settings.updateGNSSSettings == false) - { - systemPrintln("UM980 configuration maintained"); - return (true); - } - - for (int x = 0; x < 3; x++) - { - if (um980ConfigureOnce() == true) - return (true); - - // If we fail, reset UM980 - systemPrintln("Resetting UM980 to complete configuration"); - - um980Reset(); - delay(500); - um980Boot(); - delay(500); - } - - systemPrintln("UM980 failed to configure"); - return (false); -} - -bool um980ConfigureOnce() -{ - /* - Disable all message traffic - Set COM port baud rates, - UM980 COM1 - Direct to USB, 115200 - UM980 COM2 - To IMU. From settings. - UM980 COM3 - BT, config and LoRa Radio. Configured for 115200 from begin(). - Set minCNO - Set elevationAngle - Set Constellations - Set messages - Enable selected NMEA messages on COM3 - Enable selected RTCM messages on COM3 -*/ - - if (settings.debugGnss) - um980EnableDebugging(); // Print all debug to Serial - - um980DisableAllOutput(); // Disable COM1/2/3 - - bool response = true; - response &= um980->setPortBaudrate("COM1", 115200); // COM1 is connected to switch, then USB - response &= um980->setPortBaudrate("COM2", 115200); // COM2 is connected to the IMU - response &= um980->setPortBaudrate("COM3", 115200); // COM3 is connected to the switch, then ESP32 - - // For now, let's not change the baud rate of the interface. We'll be using the default 115200 for now. - response &= um980SetBaudRateCOM3(settings.dataPortBaud); // COM3 is connected to ESP UART2 - - // Enable PPS signal with a width of 200ms, and a period of 1 second - response &= um980->enablePPS(200000, 1000); // widthMicroseconds, periodMilliseconds - - response &= um980SetMinElevation(settings.minElev); // UM980 default is 5 degrees. Our default is 10. - - response &= um980SetMinCNO(settings.minCNO); - - response &= um980SetConstellations(); - - if (um980->isConfigurationPresent("CONFIG SIGNALGROUP 2") == false) - { - if (um980->sendCommand("CONFIG SIGNALGROUP 2") == false) - systemPrintln("Signal group 2 command failed"); - else - { - systemPrintln("Enabling additional reception on UM980. This can take a few seconds."); - - while (1) - { - delay(1000); // Wait for device to reboot - if (um980->isConnected() == true) - break; - else - systemPrintln("UM980 rebooting"); - } - - systemPrintln("UM980 has completed reboot."); - } - } - - if (response == true) - { - online.gnss = true; // If we failed before, mark as online now - - systemPrintln("UM980 configuration updated"); - - // Save the current configuration into non-volatile memory (NVM) - // We don't need to re-configure the UM980 at next boot - bool settingsWereSaved = um980->saveConfiguration(); - if (settingsWereSaved) - settings.updateGNSSSettings = false; - } - else - online.gnss = false; // Take it offline - - return (response); -} - -bool um980ConfigureRover() -{ - /* - Disable all message traffic - Cancel any survey-in modes - Set mode to Rover + dynamic model - Set minElevation - Enable RTCM messages on COM3 - Enable NMEA on COM3 - */ - if (online.gnss == false) - { - systemPrintln("GNSS not online"); - return (false); - } - - um980DisableAllOutput(); - - bool response = true; - - response &= um980SetModel(settings.dynamicModel); // This will cancel any base averaging mode - - response &= um980SetMinElevation(settings.minElev); // UM980 default is 5 degrees. Our default is 10. - - response &= um980SetMultipathMitigation(settings.enableMultipathMitigation); - - response &= um980SetHighAccuracyService(settings.enableGalileoHas); - - // Configure UM980 to output binary reports out COM2, connected to IM19 COM3 - response &= um980->sendCommand("BESTPOSB COM2 0.2"); // 5Hz - response &= um980->sendCommand("PSRVELB COM2 0.2"); - - // Configure UM980 to output NMEA reports out COM2, connected to IM19 COM3 - response &= um980->setNMEAPortMessage("GPGGA", "COM2", 0.2); // 5Hz - - // Enable the NMEA sentences and RTCM on COM3 last. This limits the traffic on the config - // interface port during config. - - // Only turn on messages, do not turn off messages. We assume the caller has UNLOG or similar. - response &= um980EnableRTCMRover(); - // TODO consider reducing the GSV sentence to 1/4 of the GPGGA setting - - // Only turn on messages, do not turn off messages. We assume the caller has UNLOG or similar. - response &= um980EnableNMEA(); - - // Save the current configuration into non-volatile memory (NVM) - // We don't need to re-configure the UM980 at next boot - bool settingsWereSaved = um980->saveConfiguration(); - if (settingsWereSaved) - settings.updateGNSSSettings = false; - - if (response == false) - { - systemPrintln("UM980 Rover failed to configure"); - } - - return (response); -} - -bool um980ConfigureBase() -{ - /* - Disable all messages - Start base - Enable RTCM Base messages - Enable NMEA messages - */ - - if (online.gnss == false) - { - systemPrintln("GNSS not online"); - return (false); - } - - um980DisableAllOutput(); - - bool response = true; - - // Set the dynamic mode. This will cancel any base averaging mode and is needed - // to allow a freshly started device to settle in regular GNSS reception mode before issuing - // um980BaseAverageStart(). - response &= um980SetModel(settings.dynamicModel); - - response &= um980SetMultipathMitigation(settings.enableMultipathMitigation); - - response &= um980SetHighAccuracyService(settings.enableGalileoHas); - - response &= um980EnableRTCMBase(); // Only turn on messages, do not turn off messages. We assume the caller has - // UNLOG or similar. - - // Only turn on messages, do not turn off messages. We assume the caller has UNLOG or similar. - response &= um980EnableNMEA(); - - // Save the current configuration into non-volatile memory (NVM) - // We don't need to re-configure the UM980 at next boot - bool settingsWereSaved = um980->saveConfiguration(); - if (settingsWereSaved) - settings.updateGNSSSettings = false; - - if (response == false) - { - systemPrintln("UM980 Base failed to configure"); - } - - if (settings.debugGnss) - systemPrintln("UM980 Base configured"); - - return (response); -} - -// Start a Self-optimizing Base Station -// We do not use the distance parameter (settings.observationPositionAccuracy) because that -// setting on the UM980 is related to automatically restarting base mode -// at power on (very different from ZED-F9P). -bool um980BaseAverageStart() -{ - bool response = true; - - response &= - um980->setModeBaseAverage(settings.observationSeconds); // Average for a number of seconds (default is 60) - - autoBaseStartTimer = millis(); // Stamp when averaging began - - if (response == false) - { - systemPrintln("Survey start failed"); - return (false); - } - - return (response); -} - -// Start the base using fixed coordinates -bool um980FixedBaseStart() -{ - bool response = true; - - if (settings.fixedBaseCoordinateType == COORD_TYPE_ECEF) - { - um980->setModeBaseECEF(settings.fixedEcefX, settings.fixedEcefY, settings.fixedEcefZ); - } - else if (settings.fixedBaseCoordinateType == COORD_TYPE_GEODETIC) - { - // Add height of instrument (HI) to fixed altitude - // https://www.e-education.psu.edu/geog862/node/1853 - // For example, if HAE is at 100.0m, + 2m stick + 73mm APC = 102.073 - float totalFixedAltitude = - settings.fixedAltitude + ((settings.antennaHeight_mm + settings.antennaPhaseCenter_mm) / 1000.0); - - um980->setModeBaseGeodetic(settings.fixedLat, settings.fixedLong, totalFixedAltitude); - } - - return (response); -} - -// Turn on all the enabled NMEA messages on COM3 -bool um980EnableNMEA() -{ - bool response = true; - bool gpggaEnabled = false; - bool gpzdaEnabled = false; - - for (int messageNumber = 0; messageNumber < MAX_UM980_NMEA_MSG; messageNumber++) - { - // Only turn on messages, do not turn off messages set to 0. This saves on command sending. We assume the caller - // has UNLOG or similar. - if (settings.um980MessageRatesNMEA[messageNumber] > 0) - { - if (um980->setNMEAPortMessage(umMessagesNMEA[messageNumber].msgTextName, "COM3", - settings.um980MessageRatesNMEA[messageNumber]) == false) - { - if (settings.debugGnss) - systemPrintf("Enable NMEA failed at messageNumber %d %s.\r\n", messageNumber, - umMessagesNMEA[messageNumber].msgTextName); - response &= false; // If any one of the commands fails, report failure overall - } - - // If we are using IP based corrections, we need to send local data to the PPL - // The PPL requires being fed GPGGA/ZDA, and RTCM1019/1020/1042/1046 - if (strstr(settings.pointPerfectKeyDistributionTopic, "/ip") != nullptr) - { - // Mark PPL requied messages as enabled if rate > 0 - if (strcmp(umMessagesNMEA[messageNumber].msgTextName, "GPGGA") == 0) - gpggaEnabled = true; - if (strcmp(umMessagesNMEA[messageNumber].msgTextName, "GPZDA") == 0) - gpzdaEnabled = true; - } - } - } - - if (settings.enablePointPerfectCorrections) - { - // Force on any messages that are needed for PPL - if (gpggaEnabled == false) - response &= um980->setNMEAPortMessage("GPGGA", "COM3", 1); - if (gpzdaEnabled == false) - response &= um980->setNMEAPortMessage("GPZDA", "COM3", 1); - } - - return (response); -} - -// Turn on all the enabled RTCM Rover messages on COM3 -bool um980EnableRTCMRover() -{ - bool response = true; - bool rtcm1019Enabled = false; - bool rtcm1020Enabled = false; - bool rtcm1042Enabled = false; - bool rtcm1046Enabled = false; - - for (int messageNumber = 0; messageNumber < MAX_UM980_RTCM_MSG; messageNumber++) - { - // Only turn on messages, do not turn off messages set to 0. This saves on command sending. We assume the caller - // has UNLOG or similar. - if (settings.um980MessageRatesRTCMRover[messageNumber] > 0) - { - if (um980->setRTCMPortMessage(umMessagesRTCM[messageNumber].msgTextName, "COM3", - settings.um980MessageRatesRTCMRover[messageNumber]) == false) - { - if (settings.debugGnss) - systemPrintf("Enable RTCM failed at messageNumber %d %s.", messageNumber, - umMessagesRTCM[messageNumber].msgTextName); - response &= false; // If any one of the commands fails, report failure overall - } - - // If we are using IP based corrections, we need to send local data to the PPL - // The PPL requires being fed GPGGA/ZDA, and RTCM1019/1020/1042/1046 - if (settings.enablePointPerfectCorrections) - { - // Mark PPL required messages as enabled if rate > 0 - if (strcmp(umMessagesNMEA[messageNumber].msgTextName, "RTCM1019") == 0) - rtcm1019Enabled = true; - if (strcmp(umMessagesNMEA[messageNumber].msgTextName, "RTCM1020") == 0) - rtcm1020Enabled = true; - if (strcmp(umMessagesNMEA[messageNumber].msgTextName, "RTCM1042") == 0) - rtcm1042Enabled = true; - if (strcmp(umMessagesNMEA[messageNumber].msgTextName, "RTCM1046") == 0) - rtcm1046Enabled = true; - } - } - } - - if (settings.enablePointPerfectCorrections) - { - // Force on any messages that are needed for PPL - if (rtcm1019Enabled == false) - response &= um980->setRTCMPortMessage("RTCM1019", "COM3", 1); - if (rtcm1020Enabled == false) - response &= um980->setRTCMPortMessage("RTCM1020", "COM3", 1); - if (rtcm1042Enabled == false) - response &= um980->setRTCMPortMessage("RTCM1042", "COM3", 1); - if (rtcm1046Enabled == false) - response &= um980->setRTCMPortMessage("RTCM1046", "COM3", 1); - } - - return (response); -} - -// Turn on all the enabled RTCM Base messages on COM3 -bool um980EnableRTCMBase() -{ - bool response = true; - - for (int messageNumber = 0; messageNumber < MAX_UM980_RTCM_MSG; messageNumber++) - { - // Only turn on messages, do not turn off messages set to 0. This saves on command sending. We assume the caller - // has UNLOG or similar. - if (settings.um980MessageRatesRTCMBase[messageNumber] > 0) - { - if (um980->setRTCMPortMessage(umMessagesRTCM[messageNumber].msgTextName, "COM3", - settings.um980MessageRatesRTCMBase[messageNumber]) == false) - { - if (settings.debugGnss) - systemPrintf("Enable RTCM failed at messageNumber %d %s.", messageNumber, - umMessagesRTCM[messageNumber].msgTextName); - response &= false; // If any one of the commands fails, report failure overall - } - } - } - - return (response); -} - -// Turn on all the enabled Constellations -bool um980SetConstellations() -{ - bool response = true; - - for (int constellationNumber = 0; constellationNumber < MAX_UM980_CONSTELLATIONS; constellationNumber++) - { - if (settings.um980Constellations[constellationNumber] == true) - { - if (um980->enableConstellation(um980ConstellationCommands[constellationNumber].textCommand) == false) - { - if (settings.debugGnss) - systemPrintf("Enable constellation failed at constellationNumber %d %s.", constellationNumber, - um980ConstellationCommands[constellationNumber].textName); - response &= false; // If any one of the commands fails, report failure overall - } - } - else - { - if (um980->disableConstellation(um980ConstellationCommands[constellationNumber].textCommand) == false) - { - if (settings.debugGnss) - systemPrintf("Disable constellation failed at constellationNumber %d %s.", constellationNumber, - um980ConstellationCommands[constellationNumber].textName); - response &= false; // If any one of the commands fails, report failure overall - } - } - } - - return (response); -} - -// Turn off all NMEA and RTCM -void um980DisableAllOutput() -{ - if (settings.debugGnss) - systemPrintln("UM980 disable output"); - - // Turn off local noise before moving to other ports - um980->disableOutput(); - - // Re-attempt as necessary - for (int x = 0; x < 3; x++) - { - bool response = true; - response &= um980->disableOutputPort("COM1"); - response &= um980->disableOutputPort("COM2"); - response &= um980->disableOutputPort("COM3"); - if (response == true) - break; - } -} - -// Disable all output, then re-enable -void um980DisableRTCM() -{ - um980DisableAllOutput(); - um980EnableNMEA(); -} - -bool um980SetMinElevation(uint8_t elevation) -{ - return (um980->setElevationAngle(elevation)); -} - -bool um980SetMinCNO(uint8_t minCNO) -{ - return (um980->setMinCNO(minCNO)); -} - -bool um980SetModel(uint8_t modelNumber) -{ - if (modelNumber == UM980_DYN_MODEL_SURVEY) - return (um980->setModeRoverSurvey()); - else if (modelNumber == UM980_DYN_MODEL_UAV) - return (um980->setModeRoverUAV()); - else if (modelNumber == UM980_DYN_MODEL_AUTOMOTIVE) - return (um980->setModeRoverAutomotive()); - return (false); -} - -bool um980SetMultipathMitigation(bool enableMultipathMitigation) -{ - bool result = true; - - // Enable MMP as required - if (enableMultipathMitigation == true) - { - if (um980->isConfigurationPresent("CONFIG MMP ENABLE") == false) - { - if (um980->sendCommand("CONFIG MMP ENABLE") == true) - systemPrintln("Multipath Mitigation enabled"); - else - { - systemPrintln("Multipath Mitigation failed to enable"); - result = false; - } - } - } - else - { - // Turn off MMP - if (um980->isConfigurationPresent("CONFIG MMP ENABLE") == true) - { - if (um980->sendCommand("CONFIG MMP DISABLE") == true) - systemPrintln("Multipath Mitigation disabled"); - else - { - systemPrintln("Multipath Mitigation failed to disable"); - result = false; - } - } - } - return (result); -} - -bool um980SetHighAccuracyService(bool enableGalileoHas) -{ - bool result = true; - - // Enable E6 and PPP if enabled and possible - if (settings.enableGalileoHas == true) - { - // E6 reception requires version 11833 or greater - int um980Version = String(um980->getVersion()).toInt(); // Convert the string response to a value - if (um980Version >= 11833) - { - if (um980->isConfigurationPresent("CONFIG PPP ENABLE E6-HAS") == false) - { - if (um980->sendCommand("CONFIG PPP ENABLE E6-HAS") == true) - systemPrintln("Galileo E6 service enabled"); - else - { - systemPrintln("Galileo E6 service failed to enable"); - result = false; - } - - if (um980->sendCommand("CONFIG PPP DATUM WGS84") == true) - systemPrintln("WGS84 Datum applied"); - else - { - systemPrintln("WGS84 Datum failed to apply"); - result = false; - } - } - } - else - { - systemPrintf( - "Current UM980 firmware: v%d. Galileo E6 reception requires v11833 or newer. Please update the " - "firmware on your UM980 to allow for HAS operation. Please see https://bit.ly/sfe-rtk-um980-update\r\n", - um980Version); - // Don't fail the result. Module is still configured, just without HAS. - } - } - else - { - // Turn off HAS/E6 - if (um980->isConfigurationPresent("CONFIG PPP ENABLE E6-HAS") == true) - { - if (um980->sendCommand("CONFIG PPP DISABLE") == true) - systemPrintln("Galileo E6 service disabled"); - else - { - systemPrintln("Galileo E6 service failed to disable"); - result = false; - } - } - } - return (result); -} - -void um980FactoryReset() -{ - um980->factoryReset(); - - // systemPrintln("Waiting for UM980 to reboot"); - // while (1) - // { - // delay(1000); //Wait for device to reboot - // if (um980->isConnected() == true) break; - // else systemPrintln("Device still rebooting"); - // } - // systemPrintln("UM980 has been factory reset"); -} - -// The UM980 does not have a rate setting. Instead the report rate of -// the GNSS messages can be set. For example, 0.5 is 2Hz, 0.2 is 5Hz. -// We assume, if the user wants to set the 'rate' to 5Hz, they want all -// messages set to that rate. -// All NMEA/RTCM for a rover will be based on the measurementRateMs setting -// ie, if a message != 0, then it will be output at the measurementRate. -// All RTCM for a base will be based on a measurementRateMs of 1000 with messages -// that can be reported more slowly than that (ie 1 per 10 seconds). -bool um980SetRate(double secondsBetweenSolutions) -{ - bool response = true; - - um980DisableAllOutput(); - - // Overwrite any enabled messages with this rate - for (int messageNumber = 0; messageNumber < MAX_UM980_NMEA_MSG; messageNumber++) - { - if (settings.um980MessageRatesNMEA[messageNumber] > 0) - { - settings.um980MessageRatesNMEA[messageNumber] = secondsBetweenSolutions; - } - } - response &= um980EnableNMEA(); // Enact these rates - - // TODO We don't know what state we are in, so we don't - // know which RTCM settings to update. Assume we are - // in rover for now - for (int messageNumber = 0; messageNumber < MAX_UM980_RTCM_MSG; messageNumber++) - { - if (settings.um980MessageRatesRTCMRover[messageNumber] > 0) - { - settings.um980MessageRatesRTCMRover[messageNumber] = secondsBetweenSolutions; - } - } - response &= um980EnableRTCMRover(); // Enact these rates - - // If we successfully set rates, only then record to settings - if (response == true) - { - uint16_t msBetweenSolutions = secondsBetweenSolutions * 1000; - settings.measurementRateMs = msBetweenSolutions; - } - else - { - systemPrintln("Failed to set measurement and navigation rates"); - return (false); - } - - return (true); -} - -// Returns the seconds between measurements -double um980GetRateS() -{ - return (((double)settings.measurementRateMs) / 1000.0); -} - -// Send data directly from ESP GNSS UART1 to UM980 UART3 -int um980PushRawData(uint8_t *dataToSend, int dataLength) -{ - return (serialGNSS->write(dataToSend, dataLength)); -} - -// Set the baud rate of the UM980 com port 3 -// This is used during the Bluetooth test -bool um980SetBaudRateCOM3(uint32_t baudRate) -{ - bool response = true; - - response &= um980->setPortBaudrate("COM3", baudRate); - - return (response); -} - -// Return the lower of the two Lat/Long deviations -float um980GetHorizontalAccuracy() -{ - float latitudeDeviation = um980->getLatitudeDeviation(); - float longitudeDeviation = um980->getLongitudeDeviation(); - - // The binary message may contain all 0xFFs leading to a very large negative number. - if (longitudeDeviation < -0.01) - longitudeDeviation = 50.0; - if (latitudeDeviation < -0.01) - latitudeDeviation = 50.0; - - if (longitudeDeviation < latitudeDeviation) - return (longitudeDeviation); - - return (latitudeDeviation); -} - -int um980GetSatellitesInView() -{ - return (um980->getSIV()); -} - -double um980GetLatitude() -{ - return (um980->getLatitude()); -} - -double um980GetLongitude() -{ - return (um980->getLongitude()); -} - -double um980GetAltitude() -{ - return (um980->getAltitude()); -} - -bool um980IsValidTime() -{ - if (um980->getTimeStatus() == 0) // 0 = valid, 3 = invalid - return (true); - return (false); -} - -bool um980IsValidDate() -{ - if (um980->getDateStatus() == 1) // 0 = Invalid, 1 = valid, 2 = leap second warning - return (true); - return (false); -} - -uint8_t um980GetSolutionStatus() -{ - return (um980->getSolutionStatus()); // 0 = Solution computed, 1 = Insufficient observation, 3 = No convergence, 4 = - // Covariance trace -} - -bool um980IsFullyResolved() -{ - // UM980 does not have this feature directly. - // getSolutionStatus: 0 = Solution computed, 1 = Insufficient observation, 3 = No convergence, 4 = Covariance trace - if (um980GetSolutionStatus() == 0) - return (true); - return (false); -} - -// Standard deviation of the receiver clock offset, s. -// UM980 returns seconds, ZED returns nanoseconds. We convert here to ns. -// Return just ns in uint32_t form -uint32_t um980GetTimeDeviation() -{ - double timeDeviation_s = um980->getTimeOffsetDeviation(); - // systemPrintf("um980 timeDeviation_s: %0.10f\r\n", timeDeviation_s); - if (timeDeviation_s > 1.0) - return (999999999); - - uint32_t timeDeviation_ns = timeDeviation_s * 1000000000L; // Convert to nanoseconds - // systemPrintf("um980 timeDeviation_ns: %d\r\n", timeDeviation_ns); - return (timeDeviation_ns); -} - -// 0 = None -// 16 = 3D Fix (Single) -// 49 = RTK Float (Presumed) (Wide-lane fixed solution) -// 50 = RTK Fixed (Narrow-lane fixed solution) -// Other position types, not yet seen -// 1 = FixedPos, 8 = DopplerVelocity, -// 17 = Pseudorange differential solution, 18 = SBAS, 32 = L1 float, 33 = Ionosphere-free float solution -// 34 = Narrow-land float solution, 48 = L1 fixed solution -// 68 = Precise Point Positioning solution converging -// 69 = Precise Point Positioning -uint8_t um980GetPositionType() -{ - return (um980->getPositionType()); -} - -// Return full year, ie 2023, not 23. -uint16_t um980GetYear() -{ - return (um980->getYear()); -} -uint8_t um980GetMonth() -{ - return (um980->getMonth()); -} -uint8_t um980GetDay() -{ - return (um980->getDay()); -} -uint8_t um980GetHour() -{ - return (um980->getHour()); -} -uint8_t um980GetMinute() -{ - return (um980->getMinute()); -} -uint8_t um980GetSecond() -{ - return (um980->getSecond()); -} -uint8_t um980GetMillisecond() -{ - return (um980->getMillisecond()); -} - -// Print the module type and firmware version -void um980PrintInfo() -{ - uint8_t modelType = um980->getModelType(); - - if (modelType == 18) - systemPrint("UM980"); - else - systemPrintf("Unicore Model Unknown %d", modelType); - - systemPrintf(" firmware: %s\r\n", um980->getVersion()); -} - -// Return the number of milliseconds since the data was updated -uint16_t um980FixAgeMilliseconds() -{ - return (um980->getFixAgeMilliseconds()); -} - -bool um980SaveConfiguration() -{ - return (um980->saveConfiguration()); -} - -void um980EnableDebugging() -{ - um980->enableDebugging(); // Print all debug to Serial - um980->enablePrintRxMessages(); // Print incoming processed messages from SEMP -} -void um980DisableDebugging() -{ - um980->disableDebugging(); -} - -bool um980SetModeRoverSurvey() -{ - return (um980->setModeRoverSurvey()); -} - -// If we have received serial data from the UM980 outside of the Unicore library (ie, from processUart1Message task) -// we can pass data back into the Unicore library to allow it to update its own variables -void um980UnicoreHandler(uint8_t *buffer, int length) -{ - um980->unicoreHandler(buffer, length); -} - -char *um980GetId() -{ - return (um980->getID()); -} - -void um980Boot() -{ - digitalWrite(pin_GNSS_DR_Reset, HIGH); // Tell UM980 and DR to boot -} -void um980Reset() -{ - digitalWrite(pin_GNSS_DR_Reset, LOW); // Tell UM980 and DR to reset -} - -// Query GNSS for current leap seconds -uint8_t um980GetLeapSeconds() -{ - // TODO Need to find leap seconds in UM980 - return (18); // Default to 18 -} - -uint8_t um980GetActiveMessageCount() -{ - uint8_t count = 0; - - count += um980GetActiveNmeaMessageCount(); - - count += um980GetActiveRtcmMessageCount(); - - return (count); -} - -uint8_t um980GetActiveNmeaMessageCount() -{ - uint8_t count = 0; - - for (int x = 0; x < MAX_UM980_NMEA_MSG; x++) - if (settings.um980MessageRatesNMEA[x] > 0) - count++; - - return (count); -} - -// Return true if the GPGGA message is active -bool um980IsGgaActive() -{ - if (settings.um980MessageRatesNMEA[um980GetNmeaMessageNumberByName("GPGGA")] > 0) - return (true); - return (false); -} - -uint8_t um980GetActiveRtcmMessageCount() -{ - uint8_t count = 0; - - // Determine which state we are in - if (um980InRoverMode() == true) - { - for (int x = 0; x < MAX_UM980_RTCM_MSG; x++) - if (settings.um980MessageRatesRTCMRover[x] > 0) - count++; - } - else - { - for (int x = 0; x < MAX_UM980_RTCM_MSG; x++) - if (settings.um980MessageRatesRTCMBase[x] > 0) - count++; - } - - return (count); -} - -// Control the messages that get broadcast over Bluetooth and logged (if enabled) -void um980MenuMessages() -{ - while (1) - { - systemPrintln(); - systemPrintln("Menu: GNSS Messages"); - - systemPrintf("Active messages: %d\r\n", gnssGetActiveMessageCount()); - - systemPrintln("1) Set NMEA Messages"); - systemPrintln("2) Set Rover RTCM Messages"); - systemPrintln("3) Set Base RTCM Messages"); - - systemPrintln("10) Reset to Defaults"); - - systemPrintln("x) Exit"); - - int incoming = getUserInputNumber(); // Returns EXIT, TIMEOUT, or long - - if (incoming == 1) - um980MenuMessagesSubtype(settings.um980MessageRatesNMEA, "NMEA"); - else if (incoming == 2) - um980MenuMessagesSubtype(settings.um980MessageRatesRTCMRover, "RTCMRover"); - else if (incoming == 3) - um980MenuMessagesSubtype(settings.um980MessageRatesRTCMBase, "RTCMBase"); - else if (incoming == 10) - { - // Reset rates to defaults - for (int x = 0; x < MAX_UM980_NMEA_MSG; x++) - settings.um980MessageRatesNMEA[x] = umMessagesNMEA[x].msgDefaultRate; - - // For rovers, RTCM should be zero by default. - for (int x = 0; x < MAX_UM980_RTCM_MSG; x++) - settings.um980MessageRatesRTCMRover[x] = 0; - - // Reset RTCM rates to defaults - for (int x = 0; x < MAX_UM980_RTCM_MSG; x++) - settings.um980MessageRatesRTCMBase[x] = umMessagesRTCM[x].msgDefaultRate; - - systemPrintln("Reset to Defaults"); - } - - else if (incoming == INPUT_RESPONSE_GETNUMBER_EXIT) - break; - else if (incoming == INPUT_RESPONSE_GETNUMBER_TIMEOUT) - break; - else - printUnknown(incoming); - } - - clearBuffer(); // Empty buffer of any newline chars - - // Apply these changes at menu exit - if (um980InRoverMode() == true) - restartRover = true; - else - restartBase = true; -} - -// Given a sub type (ie "RTCM", "NMEA") present menu showing messages with this subtype -// Controls the messages that get broadcast over Bluetooth and logged (if enabled) -void um980MenuMessagesSubtype(float *localMessageRate, const char *messageType) -{ - while (1) - { - systemPrintln(); - systemPrintf("Menu: Message %s\r\n", messageType); - - int endOfBlock = 0; - - if (strcmp(messageType, "NMEA") == 0) - { - endOfBlock = MAX_UM980_NMEA_MSG; - - for (int x = 0; x < MAX_UM980_NMEA_MSG; x++) - systemPrintf("%d) Message %s: %g\r\n", x + 1, umMessagesNMEA[x].msgTextName, - settings.um980MessageRatesNMEA[x]); - } - else if (strcmp(messageType, "RTCMRover") == 0) - { - endOfBlock = MAX_UM980_RTCM_MSG; - - for (int x = 0; x < MAX_UM980_RTCM_MSG; x++) - systemPrintf("%d) Message %s: %g\r\n", x + 1, umMessagesRTCM[x].msgTextName, - settings.um980MessageRatesRTCMRover[x]); - } - else if (strcmp(messageType, "RTCMBase") == 0) - { - endOfBlock = MAX_UM980_RTCM_MSG; - - for (int x = 0; x < MAX_UM980_RTCM_MSG; x++) - systemPrintf("%d) Message %s: %g\r\n", x + 1, umMessagesRTCM[x].msgTextName, - settings.um980MessageRatesRTCMBase[x]); - } - - systemPrintln("x) Exit"); - - int incoming = getUserInputNumber(); // Returns EXIT, TIMEOUT, or long - - if (incoming >= 1 && incoming <= endOfBlock) - { - // Adjust incoming to match array start of 0 - incoming--; - - // Create user prompt - char messageString[100] = ""; - if (strcmp(messageType, "NMEA") == 0) - { - sprintf(messageString, "Enter number of seconds between %s messages (0 to disable)", - umMessagesNMEA[incoming].msgTextName); - } - else if ((strcmp(messageType, "RTCMRover") == 0) || (strcmp(messageType, "RTCMBase") == 0)) - { - sprintf(messageString, "Enter number of seconds between %s messages (0 to disable)", - umMessagesRTCM[incoming].msgTextName); - } - - double newSetting = 0.0; - - // Message rates are 0.05s to 65s - if (getNewSetting(messageString, 0, 65.0, &newSetting) == INPUT_RESPONSE_VALID) - { - // Allowed values: - // 1, 0.5, 0.2, 0.1, 0.05 corresponds to 1Hz, 2Hz, 5Hz, 10Hz, 20Hz respectively. - // 1, 2, 5 corresponds to 1Hz, 0.5Hz, 0.2Hz respectively. - if (newSetting == 0.0) - { - // Allow it - } - else if (newSetting < 1.0) - { - // Deal with 0.0001 to 1.0 - if (newSetting <= 0.05) - newSetting = 0.05; // 20Hz - else if (newSetting <= 0.1) - newSetting = 0.1; // 10Hz - else if (newSetting <= 0.2) - newSetting = 0.2; // 5Hz - else if (newSetting <= 0.5) - newSetting = 0.5; // 2Hz - else - newSetting = 1.0; // 1Hz - } - // 2.7 is not allowed. Change to 2.0. - else if (newSetting >= 1.0) - newSetting = floor(newSetting); - - if (strcmp(messageType, "NMEA") == 0) - settings.um980MessageRatesNMEA[incoming] = (float)newSetting; - if (strcmp(messageType, "RTCMRover") == 0) - settings.um980MessageRatesRTCMRover[incoming] = (float)newSetting; - if (strcmp(messageType, "RTCMBase") == 0) - settings.um980MessageRatesRTCMBase[incoming] = (float)newSetting; - } - } - else if (incoming == INPUT_RESPONSE_GETNUMBER_EXIT) - break; - else if (incoming == INPUT_RESPONSE_GETNUMBER_TIMEOUT) - break; - else - printUnknown(incoming); - } - - settings.updateGNSSSettings = true; // Update the GNSS config at the next boot - - clearBuffer(); // Empty buffer of any newline chars -} - -// Returns true if the device is in Rover mode -// Currently the only two modes are Rover or Base -bool um980InRoverMode() -{ - // Determine which state we are in - if (settings.lastState == STATE_BASE_NOT_STARTED) - return (false); - - return (true); // Default to Rover -} - -char *um980GetRtcmDefaultString() -{ - return ((char *)"1005/1074/1084/1094/1124 1Hz & 1033 0.1Hz"); -} -char *um980GetRtcmLowDataRateString() -{ - return ((char *)"1074/1084/1094/1124 0.5Hz & 1005/1033 0.1Hz"); -} - -// Set RTCM for base mode to defaults (1005/1074/1084/1094/1124 1Hz & 1033 0.1Hz) -void um980BaseRtcmDefault() -{ - // Reset RTCM rates to defaults - for (int x = 0; x < MAX_UM980_RTCM_MSG; x++) - settings.um980MessageRatesRTCMBase[x] = umMessagesRTCM[x].msgDefaultRate; -} - -// Reset to Low Bandwidth Link (1074/1084/1094/1124 0.5Hz & 1005/1033 0.1Hz) -void um980BaseRtcmLowDataRate() -{ - // Zero out everything - for (int x = 0; x < MAX_UM980_RTCM_MSG; x++) - settings.um980MessageRatesRTCMBase[x] = 0; - - settings.um980MessageRatesRTCMBase[um980GetRtcmMessageNumberByName("RTCM1005")] = - 10; // 1005 0.1Hz - Exclude antenna height - settings.um980MessageRatesRTCMBase[um980GetRtcmMessageNumberByName("RTCM1074")] = 2; // 1074 0.5Hz - settings.um980MessageRatesRTCMBase[um980GetRtcmMessageNumberByName("RTCM1084")] = 2; // 1084 0.5Hz - settings.um980MessageRatesRTCMBase[um980GetRtcmMessageNumberByName("RTCM1094")] = 2; // 1094 0.5Hz - settings.um980MessageRatesRTCMBase[um980GetRtcmMessageNumberByName("RTCM1124")] = 2; // 1124 0.5Hz - settings.um980MessageRatesRTCMBase[um980GetRtcmMessageNumberByName("RTCM1033")] = 10; // 1033 0.1Hz -} - -// Given the name of an RTCM message, return the array number -uint8_t um980GetRtcmMessageNumberByName(const char *msgName) -{ - for (int x = 0; x < MAX_UM980_RTCM_MSG; x++) - { - if (strcmp(umMessagesRTCM[x].msgTextName, msgName) == 0) - return (x); - } - - systemPrintf("um980GetRtcmMessageNumberByName: %s not found\r\n", msgName); - return (0); -} - -// Given the name of an NMEA message, return the array number -uint8_t um980GetNmeaMessageNumberByName(const char *msgName) -{ - for (int x = 0; x < MAX_UM980_NMEA_MSG; x++) - { - if (strcmp(umMessagesNMEA[x].msgTextName, msgName) == 0) - return (x); - } - - systemPrintf("um980GetNmeaMessageNumberByName: %s not found\r\n", msgName); - return (0); -} - -// Controls the constellations that are used to generate a fix and logged -void um980MenuConstellations() -{ - while (1) - { - systemPrintln(); - systemPrintln("Menu: Constellations"); - - for (int x = 0; x < MAX_UM980_CONSTELLATIONS; x++) - { - systemPrintf("%d) Constellation %s: ", x + 1, um980ConstellationCommands[x].textName); - if (settings.um980Constellations[x] > 0) - systemPrint("Enabled"); - else - systemPrint("Disabled"); - systemPrintln(); - } - - if (present.galileoHasCapable) - { - systemPrintf("%d) Galileo E6 Corrections: %s\r\n", MAX_UM980_CONSTELLATIONS + 1, - settings.enableGalileoHas ? "Enabled" : "Disabled"); - } - - systemPrintln("x) Exit"); - - int incoming = getUserInputNumber(); // Returns EXIT, TIMEOUT, or long - - if (incoming >= 1 && incoming <= MAX_UM980_CONSTELLATIONS) - { - incoming--; // Align choice to constellation array of 0 to 5 - - settings.um980Constellations[incoming] ^= 1; - } - else if ((incoming == MAX_UM980_CONSTELLATIONS + 1) && present.galileoHasCapable) - { - settings.enableGalileoHas ^= 1; - } - else if (incoming == INPUT_RESPONSE_GETNUMBER_EXIT) - break; - else if (incoming == INPUT_RESPONSE_GETNUMBER_TIMEOUT) - break; - else - printUnknown(incoming); - } - - // Apply current settings to module - gnssSetConstellations(); - - clearBuffer(); // Empty buffer of any newline chars -} - -#endif // COMPILE_UM980 - -// Check if updateUm980Firmware.txt exists -bool checkUpdateUm980Firmware() -{ - if (online.fs == false) - return false; - - if (LittleFS.exists("/updateUm980Firmware.txt")) - { - if (settings.debugGnss) - systemPrintln("LittleFS updateUm980Firmware.txt exists"); - - // We do not remove the file here. See removeupdateUm980Firmware(). - - return true; - } - - return false; -} - -void removeUpdateUm980Firmware() -{ - if (online.fs == false) - return; - - if (LittleFS.exists("/updateUm980Firmware.txt")) - { - if (settings.debugGnss) - systemPrintln("Removing updateUm980Firmware.txt "); - - LittleFS.remove("/updateUm980Firmware.txt"); - } -} - -// Force UART connection to UM980 for firmware update on the next boot by creating updateUm980Firmware.txt in -// LittleFS -bool createUm980Passthrough() -{ - if (online.fs == false) - return false; - - if (LittleFS.exists("/updateUm980Firmware.txt")) - { - if (settings.debugGnss) - systemPrintln("LittleFS updateUm980Firmware.txt already exists"); - return true; - } - - File updateUm980Firmware = LittleFS.open("/updateUm980Firmware.txt", FILE_WRITE); - updateUm980Firmware.close(); - - if (LittleFS.exists("/updateUm980Firmware.txt")) - return true; - - if (settings.debugGnss) - systemPrintln("Unable to create updateUm980Firmware.txt on LittleFS"); - return false; -} - -void beginUm980FirmwareUpdate() -{ - // Note: We cannot increase the bootloading speed beyond 115200 because - // we would need to alter the UM980 baud, then save to NVM, then allow the UM980 to reset. - // This is workable, but the next time the RTK Torch resets, it assumes communication at 115200bps - // This fails and communication is broken. We could program in some logic that attempts comm at 460800 - // then reconfigures the UM980 to 115200bps, then resets, but autobaud detection in the UM980 library is - // not yet supported. - - // Stop all UART tasks - tasksStopGnssUart(); - - systemPrintln(); - systemPrintln("Entering UM980 direct connect for firmware update and configuration. Disconnect this terminal " - "connection. Use " - "UPrecise to update the firmware. Baudrate: 115200bps. Press the power button to return " - "to normal operation."); - systemFlush(); - - // Make sure ESP-UART1 is connected to UM980 - muxSelectUm980(); - - if (serialGNSS == nullptr) - serialGNSS = new HardwareSerial(2); // Use UART2 on the ESP32 for communication with the GNSS module - - serialGNSS->begin(115200, SERIAL_8N1, pin_GnssUart_RX, pin_GnssUart_TX); - - // UPrecise needs to query the device before entering bootload mode - // Wait for UPrecise to send bootloader trigger (character T followed by character @) before resetting UM980 - bool inBootMode = false; - - // Echo everything to/from UM980 - while (1) - { - // Data coming from UM980 to external USB - if (serialGNSS->available()) - Serial.write(serialGNSS->read()); - - // Data coming from external USB to UM980 - if (Serial.available()) - { - byte incoming = Serial.read(); - serialGNSS->write(incoming); - - // Detect bootload sequence - if (inBootMode == false && incoming == 'T') - { - byte nextIncoming = Serial.peek(); - if (nextIncoming == '@') - { - // Reset UM980 - um980Reset(); - delay(25); - um980Boot(); - - inBootMode = true; - } - } - } - - if (digitalRead(pin_powerButton) == HIGH) - { - while (digitalRead(pin_powerButton) == HIGH) - delay(100); - - // Remove file and reset to exit pass-through mode - removeUpdateUm980Firmware(); - - // Beep to indicate exit - beepOn(); - delay(300); - beepOff(); - delay(100); - beepOn(); - delay(300); - beepOff(); - - systemPrintln("Exiting UM980 passthrough mode"); - systemFlush(); // Complete prints - - ESP.restart(); - } - } - - systemFlush(); // Complete prints -} diff --git a/Firmware/RTK_Everywhere/ZED.ino b/Firmware/RTK_Everywhere/ZED.ino deleted file mode 100644 index 4149a8b35..000000000 --- a/Firmware/RTK_Everywhere/ZED.ino +++ /dev/null @@ -1,1958 +0,0 @@ -// These globals are updated regularly via the storePVTdata callback -double zedLatitude; -double zedLongitude; -float zedAltitude; -float zedHorizontalAccuracy; - -uint8_t zedDay; -uint8_t zedMonth; -uint16_t zedYear; -uint8_t zedHour; -uint8_t zedMinute; -uint8_t zedSecond; -int32_t zedNanosecond; -uint16_t zedMillisecond; // Limited to first two digits - -uint8_t zedSatellitesInView; -uint8_t zedFixType; -uint8_t zedCarrierSolution; - -bool zedValidDate; -bool zedValidTime; -bool zedConfirmedDate; -bool zedConfirmedTime; -bool zedFullyResolved; -uint32_t zedTAcc; - -unsigned long zedPvtArrivalMillis = 0; -bool pvtUpdated = false; - -// Below are the callbacks specific to the ZED-F9x -// Once called, they update global variables that are then accessed via zedGetSatellitesInView() and the likes -//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- -// These are the callbacks that get regularly called, globals are updated -void storePVTdata(UBX_NAV_PVT_data_t *ubxDataStruct) -{ - zedAltitude = ubxDataStruct->height / 1000.0; - - zedDay = ubxDataStruct->day; - zedMonth = ubxDataStruct->month; - zedYear = ubxDataStruct->year; - - zedHour = ubxDataStruct->hour; - zedMinute = ubxDataStruct->min; - zedSecond = ubxDataStruct->sec; - zedNanosecond = ubxDataStruct->nano; - zedMillisecond = ceil((ubxDataStruct->iTOW % 1000) / 10.0); // Limit to first two digits - - zedSatellitesInView = ubxDataStruct->numSV; - zedFixType = ubxDataStruct->fixType; // 0 = no fix, 1 = dead reckoning only, 2 = 2D-fix, 3 = 3D-fix, 4 = GNSS + dead - // reckoning combined, 5 = time only fix - zedCarrierSolution = ubxDataStruct->flags.bits.carrSoln; - - zedValidDate = ubxDataStruct->valid.bits.validDate; - zedValidTime = ubxDataStruct->valid.bits.validTime; - zedConfirmedDate = ubxDataStruct->flags2.bits.confirmedDate; - zedConfirmedTime = ubxDataStruct->flags2.bits.confirmedTime; - zedFullyResolved = ubxDataStruct->valid.bits.fullyResolved; - zedTAcc = ubxDataStruct->tAcc; // Nanoseconds - - zedPvtArrivalMillis = millis(); - pvtUpdated = true; -} - -void storeHPdata(UBX_NAV_HPPOSLLH_data_t *ubxDataStruct) -{ - zedHorizontalAccuracy = ((float)ubxDataStruct->hAcc) / 10000.0; // Convert hAcc from mm*0.1 to m - - zedLatitude = ((double)ubxDataStruct->lat) / 10000000.0; - zedLatitude += ((double)ubxDataStruct->latHp) / 1000000000.0; - zedLongitude = ((double)ubxDataStruct->lon) / 10000000.0; - zedLongitude += ((double)ubxDataStruct->lonHp) / 1000000000.0; -} - -void storeTIMTPdata(UBX_TIM_TP_data_t *ubxDataStruct) -{ - uint32_t tow = ubxDataStruct->week - SFE_UBLOX_JAN_1ST_2020_WEEK; // Calculate the number of weeks since Jan 1st - // 2020 - tow *= SFE_UBLOX_SECS_PER_WEEK; // Convert weeks to seconds - tow += SFE_UBLOX_EPOCH_WEEK_2086; // Add the TOW for Jan 1st 2020 - tow += ubxDataStruct->towMS / 1000; // Add the TOW for the next TP - - uint32_t us = ubxDataStruct->towMS % 1000; // Extract the milliseconds - us *= 1000; // Convert to microseconds - - double subMS = ubxDataStruct->towSubMS; // Get towSubMS (ms * 2^-32) - subMS *= pow(2.0, -32.0); // Convert to milliseconds - subMS *= 1000; // Convert to microseconds - - us += (uint32_t)subMS; // Add subMS - - timTpEpoch = tow; - timTpMicros = us; - timTpArrivalMillis = millis(); - timTpUpdated = true; -} - -void storeMONHWdata(UBX_MON_HW_data_t *ubxDataStruct) -{ - aStatus = ubxDataStruct->aStatus; -} - -void storeRTCM1005data(RTCM_1005_data_t *rtcmData1005) -{ - ARPECEFX = rtcmData1005->AntennaReferencePointECEFX; - ARPECEFY = rtcmData1005->AntennaReferencePointECEFY; - ARPECEFZ = rtcmData1005->AntennaReferencePointECEFZ; - ARPECEFH = 0; - newARPAvailable = true; -} - -void storeRTCM1006data(RTCM_1006_data_t *rtcmData1006) -{ - ARPECEFX = rtcmData1006->AntennaReferencePointECEFX; - ARPECEFY = rtcmData1006->AntennaReferencePointECEFY; - ARPECEFZ = rtcmData1006->AntennaReferencePointECEFZ; - ARPECEFH = rtcmData1006->AntennaHeight; - newARPAvailable = true; -} -//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- - -void zedBegin() -{ - // Instantiate the library - if (theGNSS == nullptr) - theGNSS = new SFE_UBLOX_GNSS_SUPER_DERIVED(); - - // Note: we don't need to skip this for configureViaEthernet because the ZED is on I2C only - not SPI - - if (theGNSS->begin(*i2c_0) == false) - { - log_d("GNSS Failed to begin. Trying again."); - - // Try again with power on delay - delay(1000); // Wait for ZED-F9P to power up before it can respond to ACK - if (theGNSS->begin(*i2c_0) == false) - { - log_d("GNSS offline"); - displayGNSSFail(1000); - return; - } - } - - // Increase transactions to reduce transfer time - theGNSS->i2cTransactionSize = 128; - - // Auto-send Valset messages before the buffer is completely full - theGNSS->autoSendCfgValsetAtSpaceRemaining(16); - - // Check the firmware version of the ZED-F9P. Based on Example21_ModuleInfo. - if (theGNSS->getModuleInfo(1100) == true) // Try to get the module info - { - // Reconstruct the firmware version - snprintf(gnssFirmwareVersion, sizeof(gnssFirmwareVersion), "%s %d.%02d", theGNSS->getFirmwareType(), - theGNSS->getFirmwareVersionHigh(), theGNSS->getFirmwareVersionLow()); - - gnssFirmwareVersionInt = (theGNSS->getFirmwareVersionHigh() * 100) + theGNSS->getFirmwareVersionLow(); - - // Check if this is known firmware - //"1.20" - Mostly for F9R HPS 1.20, but also F9P HPG v1.20 - //"1.21" - F9R HPS v1.21 - //"1.30" - ZED-F9P (HPG) released Dec, 2021. Also ZED-F9R (HPS) released Sept, 2022 - //"1.32" - ZED-F9P released May, 2022 - //"1.50" - ZED-F9P released July, 2024 - - const uint8_t knownFirmwareVersions[] = {100, 112, 113, 120, 121, 130, 132, 150}; - bool knownFirmware = false; - for (uint8_t i = 0; i < (sizeof(knownFirmwareVersions) / sizeof(uint8_t)); i++) - { - if (gnssFirmwareVersionInt == knownFirmwareVersions[i]) - knownFirmware = true; - } - - if (!knownFirmware) - { - systemPrintf("Unknown firmware version: %s\r\n", gnssFirmwareVersion); - gnssFirmwareVersionInt = 99; // 0.99 invalid firmware version - } - - // Determine if we have a ZED-F9P or an ZED-F9R - if (strstr(theGNSS->getModuleName(), "ZED-F9P") == nullptr) - systemPrintf("Unknown ZED module: %s\r\n", theGNSS->getModuleName()); - - if (strcmp(theGNSS->getFirmwareType(), "HPG") == 0) - if ((theGNSS->getFirmwareVersionHigh() == 1) && (theGNSS->getFirmwareVersionLow() < 30)) - { - systemPrintln( - "ZED-F9P module is running old firmware which does not support SPARTN. Please upgrade: " - "https://docs.sparkfun.com/SparkFun_RTK_Firmware/firmware_update/#updating-u-blox-firmware"); - displayUpdateZEDF9P(3000); - } - - if (strcmp(theGNSS->getFirmwareType(), "HPS") == 0) - if ((theGNSS->getFirmwareVersionHigh() == 1) && (theGNSS->getFirmwareVersionLow() < 21)) - { - systemPrintln( - "ZED-F9R module is running old firmware which does not support SPARTN. Please upgrade: " - "https://docs.sparkfun.com/SparkFun_RTK_Firmware/firmware_update/#updating-u-blox-firmware"); - displayUpdateZEDF9R(3000); - } - - zedPrintInfo(); // Print module type and firmware version - } - - UBX_SEC_UNIQID_data_t chipID; - if (theGNSS->getUniqueChipId(&chipID)) - { - snprintf(gnssUniqueId, sizeof(gnssUniqueId), "%s", theGNSS->getUniqueChipIdStr(&chipID)); - } - - systemPrintln("GNSS ZED online"); - - online.gnss = true; -} - -// Setup the u-blox module for any setup (base or rover) -// In general we check if the setting is incorrect before writing it. Otherwise, the set commands have, on rare -// occasion, become corrupt. The worst is when the I2C port gets turned off or the I2C address gets borked. -bool zedConfigure() -{ - if (online.gnss == false) - return (false); - - bool response = true; - - // Turn on/off debug messages - if (settings.debugGnss) - theGNSS->enableDebugging(Serial, true); // Enable only the critical debug messages over Serial - else - theGNSS->disableDebugging(); - - // Check if the ubxMessageRates or ubxMessageRatesBase need to be defaulted - // Redundant - also done by gnssConfigure - // checkGNSSArrayDefaults(); - - theGNSS->setAutoPVTcallbackPtr(&storePVTdata); // Enable automatic NAV PVT messages with callback to storePVTdata - theGNSS->setAutoHPPOSLLHcallbackPtr( - &storeHPdata); // Enable automatic NAV HPPOSLLH messages with callback to storeHPdata - theGNSS->setRTCM1005InputcallbackPtr( - &storeRTCM1005data); // Configure a callback for RTCM 1005 - parsed from pushRawData - theGNSS->setRTCM1006InputcallbackPtr( - &storeRTCM1006data); // Configure a callback for RTCM 1006 - parsed from pushRawData - - if (present.timePulseInterrupt == true) - theGNSS->setAutoTIMTPcallbackPtr( - &storeTIMTPdata); // Enable automatic TIM TP messages with callback to storeTIMTPdata - - // Configuring the ZED can take more than 2000ms. We save configuration to - // ZED so there is no need to update settings unless user has modified - // the settings file or internal settings. - if (settings.updateGNSSSettings == false) - { - systemPrintln("ZED-F9x configuration maintained"); - return (true); - } - - // Wait for initial report from module - int maxWait = 2000; - startTime = millis(); - while (pvtUpdated == false) - { - gnssUpdate(); // Regularly poll to get latest data - - delay(10); - if ((millis() - startTime) > maxWait) - { - log_d("PVT Update failed"); - break; - } - } - - // The first thing we do is go to 1Hz to lighten any I2C traffic from a previous configuration - response &= theGNSS->newCfgValset(); - response &= theGNSS->addCfgValset(UBLOX_CFG_RATE_MEAS, 1000); - response &= theGNSS->addCfgValset(UBLOX_CFG_RATE_NAV, 1); - - if (commandSupported(UBLOX_CFG_TMODE_MODE) == true) - response &= theGNSS->addCfgValset(UBLOX_CFG_TMODE_MODE, 0); // Disable survey-in mode - - // UART1 will primarily be used to pass NMEA and UBX from ZED to ESP32 (eventually to cell phone) - // but the phone can also provide RTCM data and a user may want to configure the ZED over Bluetooth. - // So let's be sure to enable UBX+NMEA+RTCM on the input - response &= theGNSS->addCfgValset(UBLOX_CFG_UART1OUTPROT_UBX, 1); - response &= theGNSS->addCfgValset(UBLOX_CFG_UART1OUTPROT_NMEA, 1); - if (commandSupported(UBLOX_CFG_UART1OUTPROT_RTCM3X) == true) - response &= theGNSS->addCfgValset(UBLOX_CFG_UART1OUTPROT_RTCM3X, 1); - response &= theGNSS->addCfgValset(UBLOX_CFG_UART1INPROT_UBX, 1); - response &= theGNSS->addCfgValset(UBLOX_CFG_UART1INPROT_NMEA, 1); - response &= theGNSS->addCfgValset(UBLOX_CFG_UART1INPROT_RTCM3X, 1); - if (commandSupported(UBLOX_CFG_UART1INPROT_SPARTN) == true) - response &= theGNSS->addCfgValset(UBLOX_CFG_UART1INPROT_SPARTN, 0); - - response &= theGNSS->addCfgValset(UBLOX_CFG_UART1_BAUDRATE, - settings.dataPortBaud); // Defaults to 230400 to maximize message output support - response &= theGNSS->addCfgValset( - UBLOX_CFG_UART2_BAUDRATE, - settings.radioPortBaud); // Defaults to 57600 to match SiK telemetry radio firmware default - - // Disable SPI port - This is just to remove some overhead by ZED - response &= theGNSS->addCfgValset(UBLOX_CFG_SPIOUTPROT_UBX, 0); - response &= theGNSS->addCfgValset(UBLOX_CFG_SPIOUTPROT_NMEA, 0); - if (commandSupported(UBLOX_CFG_SPIOUTPROT_RTCM3X) == true) - response &= theGNSS->addCfgValset(UBLOX_CFG_SPIOUTPROT_RTCM3X, 0); - - response &= theGNSS->addCfgValset(UBLOX_CFG_SPIINPROT_UBX, 0); - response &= theGNSS->addCfgValset(UBLOX_CFG_SPIINPROT_NMEA, 0); - response &= theGNSS->addCfgValset(UBLOX_CFG_SPIINPROT_RTCM3X, 0); - if (commandSupported(UBLOX_CFG_SPIINPROT_SPARTN) == true) - response &= theGNSS->addCfgValset(UBLOX_CFG_SPIINPROT_SPARTN, 0); - - // Set the UART2 to only do RTCM (in case this device goes into base mode) - response &= theGNSS->addCfgValset(UBLOX_CFG_UART2OUTPROT_UBX, 0); - response &= theGNSS->addCfgValset(UBLOX_CFG_UART2OUTPROT_NMEA, 0); - if (commandSupported(UBLOX_CFG_UART2OUTPROT_RTCM3X) == true) - response &= theGNSS->addCfgValset(UBLOX_CFG_UART2OUTPROT_RTCM3X, 1); - response &= theGNSS->addCfgValset(UBLOX_CFG_UART2INPROT_UBX, settings.enableUART2UBXIn); - response &= theGNSS->addCfgValset(UBLOX_CFG_UART2INPROT_NMEA, 0); - response &= theGNSS->addCfgValset(UBLOX_CFG_UART2INPROT_RTCM3X, 1); - if (commandSupported(UBLOX_CFG_UART2INPROT_SPARTN) == true) - response &= theGNSS->addCfgValset(UBLOX_CFG_UART2INPROT_SPARTN, 0); - - // We don't want NMEA over I2C, but we will want to deliver RTCM, and UBX+RTCM is not an option - response &= theGNSS->addCfgValset(UBLOX_CFG_I2COUTPROT_UBX, 1); - response &= theGNSS->addCfgValset(UBLOX_CFG_I2COUTPROT_NMEA, 1); - if (commandSupported(UBLOX_CFG_I2COUTPROT_RTCM3X) == true) - response &= theGNSS->addCfgValset(UBLOX_CFG_I2COUTPROT_RTCM3X, 1); - - response &= theGNSS->addCfgValset(UBLOX_CFG_I2CINPROT_UBX, 1); - response &= theGNSS->addCfgValset(UBLOX_CFG_I2CINPROT_NMEA, 1); - response &= theGNSS->addCfgValset(UBLOX_CFG_I2CINPROT_RTCM3X, 1); - - if (commandSupported(UBLOX_CFG_I2CINPROT_SPARTN) == true) - { - if (present.lband_neo == true) - response &= - theGNSS->addCfgValset(UBLOX_CFG_I2CINPROT_SPARTN, - 1); // We push NEO-D9S correction data (SPARTN) to ZED-F9P over the I2C interface - else - response &= theGNSS->addCfgValset(UBLOX_CFG_I2CINPROT_SPARTN, 0); - } - - // The USB port on the ZED may be used for RTCM to/from the computer (as an NTRIP caster or client) - // So let's be sure all protocols are on for the USB port - response &= theGNSS->addCfgValset(UBLOX_CFG_USBOUTPROT_UBX, 1); - response &= theGNSS->addCfgValset(UBLOX_CFG_USBOUTPROT_NMEA, 1); - if (commandSupported(UBLOX_CFG_USBOUTPROT_RTCM3X) == true) - response &= theGNSS->addCfgValset(UBLOX_CFG_USBOUTPROT_RTCM3X, 1); - response &= theGNSS->addCfgValset(UBLOX_CFG_USBINPROT_UBX, 1); - response &= theGNSS->addCfgValset(UBLOX_CFG_USBINPROT_NMEA, 1); - response &= theGNSS->addCfgValset(UBLOX_CFG_USBINPROT_RTCM3X, 1); - if (commandSupported(UBLOX_CFG_USBINPROT_SPARTN) == true) - response &= theGNSS->addCfgValset(UBLOX_CFG_USBINPROT_SPARTN, 0); - - if (commandSupported(UBLOX_CFG_NAVSPG_INFIL_MINCNO) == true) - { - response &= - theGNSS->addCfgValset(UBLOX_CFG_NAVSPG_INFIL_MINCNO, - settings.minCNO); // Set minimum satellite signal level for navigation - default 6 - } - - if (commandSupported(UBLOX_CFG_NAV2_OUT_ENABLED) == true) - { - // Count NAV2 messages and enable NAV2 as needed. - if (zedGetNAV2MessageCount() > 0) - { - response &= theGNSS->addCfgValset( - UBLOX_CFG_NAV2_OUT_ENABLED, - 1); // Enable NAV2 messages. This has the side effect of causing RTCM to generate twice as fast. - } - else - response &= theGNSS->addCfgValset(UBLOX_CFG_NAV2_OUT_ENABLED, 0); // Disable NAV2 messages - } - - response &= theGNSS->sendCfgValset(); - - if (response == false) - systemPrintln("Module failed config block 0"); - response = true; // Reset - - // Enable the constellations the user has set - response &= zedSetConstellations(true); // 19 messages. Send newCfg or sendCfg with value set - if (response == false) - systemPrintln("Module failed config block 1"); - response = true; // Reset - - // Make sure the appropriate messages are enabled - response &= zedSetMessages(MAX_SET_MESSAGES_RETRIES); // Does a complete open/closed val set - if (response == false) - systemPrintln("Module failed config block 2"); - response = true; // Reset - - // Disable NMEA messages on all but UART1 - response &= theGNSS->newCfgValset(); - - response &= theGNSS->addCfgValset(UBLOX_CFG_MSGOUT_NMEA_ID_GGA_I2C, 0); - response &= theGNSS->addCfgValset(UBLOX_CFG_MSGOUT_NMEA_ID_GSA_I2C, 0); - response &= theGNSS->addCfgValset(UBLOX_CFG_MSGOUT_NMEA_ID_GSV_I2C, 0); - response &= theGNSS->addCfgValset(UBLOX_CFG_MSGOUT_NMEA_ID_RMC_I2C, 0); - response &= theGNSS->addCfgValset(UBLOX_CFG_MSGOUT_NMEA_ID_GST_I2C, 0); - response &= theGNSS->addCfgValset(UBLOX_CFG_MSGOUT_NMEA_ID_GLL_I2C, 0); - response &= theGNSS->addCfgValset(UBLOX_CFG_MSGOUT_NMEA_ID_VTG_I2C, 0); - - response &= theGNSS->addCfgValset(UBLOX_CFG_MSGOUT_NMEA_ID_GGA_UART2, 0); - response &= theGNSS->addCfgValset(UBLOX_CFG_MSGOUT_NMEA_ID_GSA_UART2, 0); - response &= theGNSS->addCfgValset(UBLOX_CFG_MSGOUT_NMEA_ID_GSV_UART2, 0); - response &= theGNSS->addCfgValset(UBLOX_CFG_MSGOUT_NMEA_ID_RMC_UART2, 0); - response &= theGNSS->addCfgValset(UBLOX_CFG_MSGOUT_NMEA_ID_GST_UART2, 0); - response &= theGNSS->addCfgValset(UBLOX_CFG_MSGOUT_NMEA_ID_GLL_UART2, 0); - response &= theGNSS->addCfgValset(UBLOX_CFG_MSGOUT_NMEA_ID_VTG_UART2, 0); - - response &= theGNSS->addCfgValset(UBLOX_CFG_MSGOUT_NMEA_ID_GGA_SPI, 0); - response &= theGNSS->addCfgValset(UBLOX_CFG_MSGOUT_NMEA_ID_GSA_SPI, 0); - response &= theGNSS->addCfgValset(UBLOX_CFG_MSGOUT_NMEA_ID_GSV_SPI, 0); - response &= theGNSS->addCfgValset(UBLOX_CFG_MSGOUT_NMEA_ID_RMC_SPI, 0); - response &= theGNSS->addCfgValset(UBLOX_CFG_MSGOUT_NMEA_ID_GST_SPI, 0); - response &= theGNSS->addCfgValset(UBLOX_CFG_MSGOUT_NMEA_ID_GLL_SPI, 0); - response &= theGNSS->addCfgValset(UBLOX_CFG_MSGOUT_NMEA_ID_VTG_SPI, 0); - - response &= theGNSS->sendCfgValset(); - - if (response == false) - systemPrintln("Module failed config block 3"); - - if (present.antennaShortOpen) - { - theGNSS->newCfgValset(); - - theGNSS->addCfgValset(UBLOX_CFG_HW_ANT_CFG_SHORTDET, 1); // Enable antenna short detection - theGNSS->addCfgValset(UBLOX_CFG_HW_ANT_CFG_OPENDET, 1); // Enable antenna open detection - - if (theGNSS->sendCfgValset()) - { - theGNSS->setAutoMONHWcallbackPtr( - &storeMONHWdata); // Enable automatic MON HW messages with callback to storeMONHWdata - } - else - { - systemPrintln("Failed to configure GNSS antenna detection"); - } - } - - if (response == true) - systemPrintln("ZED-F9x configuration update"); - - return (response); -} - -// Configure specific aspects of the receiver for rover mode -bool zedConfigureRover() -{ - if (online.gnss == false) - { - log_d("GNSS not online"); - return (false); - } - - // If our settings haven't changed, and this is first config since power on, trust GNSS's settings - if (settings.updateGNSSSettings == false && firstPowerOn == true) - { - firstPowerOn = false; // Next time user switches modes, new settings will be applied - log_d("Skipping ZED Rover configuration"); - return (true); - } - - firstPowerOn = false; // If we switch between rover/base in the future, force config of module. - - gnssUpdate(); // Regularly poll to get latest data - - bool success = false; - int tryNo = -1; - - // Try up to MAX_SET_MESSAGES_RETRIES times to configure the GNSS - // This corrects occasional failures seen on the Reference Station where the GNSS is connected via SPI - // instead of I2C and UART1. I believe the SETVAL ACK is occasionally missed due to the level of messages being - // processed. - while ((++tryNo < MAX_SET_MESSAGES_RETRIES) && !success) - { - bool response = true; - - // Set output rate - response &= theGNSS->newCfgValset(); - response &= theGNSS->addCfgValset(UBLOX_CFG_RATE_MEAS, settings.measurementRateMs); - response &= theGNSS->addCfgValset(UBLOX_CFG_RATE_NAV, settings.navigationRate); - - // Survey mode is only available on ZED-F9P modules - if (commandSupported(UBLOX_CFG_TMODE_MODE) == true) - response &= theGNSS->addCfgValset(UBLOX_CFG_TMODE_MODE, 0); // Disable survey-in mode - - response &= - theGNSS->addCfgValset(UBLOX_CFG_NAVSPG_DYNMODEL, (dynModel)settings.dynamicModel); // Set dynamic model - - // RTCM is only available on ZED-F9P modules - // - // For most RTK products, the GNSS is interfaced via both I2C and UART1. Configuration and PVT/HPPOS messages - // are configured over I2C. Any messages that need to be logged are output on UART1, and received by this code - // using serialGNSS-> So in Rover mode, we want to disable any RTCM messages on I2C (and USB and UART2). - // - // But, on the Reference Station, the GNSS is interfaced via SPI. It has no access to I2C and UART1. So for that - // product - in Rover mode - we want to leave any RTCM messages enabled on SPI so they can be logged if desired. - - // Find first RTCM record in ubxMessage array - int firstRTCMRecord = zedGetMessageNumberByName("RTCM_1005"); - - // Set RTCM messages to user's settings - for (int x = 0; x < MAX_UBX_MSG_RTCM; x++) - response &= - theGNSS->addCfgValset(ubxMessages[firstRTCMRecord + x].msgConfigKey - 1, - settings.ubxMessageRates[firstRTCMRecord + x]); // UBLOX_CFG UART1 - 1 = I2C - - // Set RTCM messages to user's settings - for (int x = 0; x < MAX_UBX_MSG_RTCM; x++) - { - response &= - theGNSS->addCfgValset(ubxMessages[firstRTCMRecord + x].msgConfigKey + 1, - settings.ubxMessageRates[firstRTCMRecord + x]); // UBLOX_CFG UART1 + 1 = UART2 - response &= - theGNSS->addCfgValset(ubxMessages[firstRTCMRecord + x].msgConfigKey + 2, - settings.ubxMessageRates[firstRTCMRecord + x]); // UBLOX_CFG UART1 + 2 = USB - } - - response &= theGNSS->addCfgValset(UBLOX_CFG_NMEA_MAINTALKERID, - 3); // Return talker ID to GNGGA after NTRIP Client set to GPGGA - - response &= theGNSS->addCfgValset(UBLOX_CFG_NMEA_HIGHPREC, 1); // Enable high precision NMEA - response &= theGNSS->addCfgValset(UBLOX_CFG_NMEA_SVNUMBERING, 1); // Enable extended satellite numbering - - response &= theGNSS->addCfgValset(UBLOX_CFG_NAVSPG_INFIL_MINELEV, settings.minElev); // Set minimum elevation - - response &= theGNSS->sendCfgValset(); // Closing - - if (response) - success = true; - } - - if (!success) - log_d("Rover config failed 1"); - - if (!success) - systemPrintln("Rover config fail"); - - return (success); -} - -// Configure specific aspects of the receiver for base mode -bool zedConfigureBase() -{ - if (online.gnss == false) - return (false); - - // If our settings haven't changed, and this is first config since power on, trust ZED's settings - if (settings.updateGNSSSettings == false && firstPowerOn == true) - { - firstPowerOn = false; // Next time user switches modes, new settings will be applied - log_d("Skipping ZED Base configuration"); - return (true); - } - - firstPowerOn = false; // If we switch between rover/base in the future, force config of module. - - gnssUpdate(); // Regularly poll to get latest data - - theGNSS->setNMEAGPGGAcallbackPtr( - nullptr); // Disable GPGGA call back that may have been set during Rover NTRIP Client mode - - bool success = false; - int tryNo = -1; - - // Try up to MAX_SET_MESSAGES_RETRIES times to configure the GNSS - // This corrects occasional failures seen on the Reference Station where the GNSS is connected via SPI - // instead of I2C and UART1. I believe the SETVAL ACK is occasionally missed due to the level of messages being - // processed. - while ((++tryNo < MAX_SET_MESSAGES_RETRIES) && !success) - { - bool response = true; - - // In Base mode we force 1Hz - response &= theGNSS->newCfgValset(); - response &= theGNSS->addCfgValset(UBLOX_CFG_RATE_MEAS, 1000); - response &= theGNSS->addCfgValset(UBLOX_CFG_RATE_NAV, 1); - - // Since we are at 1Hz, allow GSV NMEA to be reported at whatever the user has chosen - response &= theGNSS->addCfgValset(ubxMessages[8].msgConfigKey, - settings.ubxMessageRates[8]); // Update rate on module - - response &= - theGNSS->addCfgValset(UBLOX_CFG_MSGOUT_NMEA_ID_GGA_I2C, - 0); // Disable NMEA message that may have been set during Rover NTRIP Client mode - - // Survey mode is only available on ZED-F9P modules - if (commandSupported(UBLOX_CFG_TMODE_MODE) == true) - response &= theGNSS->addCfgValset(UBLOX_CFG_TMODE_MODE, 0); // Disable survey-in mode - - // Note that using UBX-CFG-TMODE3 to set the receiver mode to Survey In or to Fixed Mode, will set - // automatically the dynamic platform model (CFG-NAVSPG-DYNMODEL) to Stationary. - // response &= theGNSS->addCfgValset(UBLOX_CFG_NAVSPG_DYNMODEL, (dynModel)settings.dynamicModel); //Not needed - - // For most RTK products, the GNSS is interfaced via both I2C and UART1. Configuration and PVT/HPPOS messages - // are configured over I2C. Any messages that need to be logged are output on UART1, and received by this code - // using serialGNSS-> In base mode the RTK device should output RTCM over all ports: (Primary) UART2 in case the - // RTK device is connected via radio to rover (Optional) I2C in case user wants base to connect to WiFi and - // NTRIP Caster (Seconday) USB in case the RTK device is used as an NTRIP caster connected to SBC or other - // (Tertiary) UART1 in case RTK device is sending RTCM to a phone that is then NTRIP Caster - - // Find first RTCM record in ubxMessage array - int firstRTCMRecord = zedGetMessageNumberByName("RTCM_1005"); - - // ubxMessageRatesBase is an array of ~12 uint8_ts - // ubxMessage is an array of ~80 messages - // We use firstRTCMRecord as an offset for the keys, but use x as the rate - - for (int x = 0; x < MAX_UBX_MSG_RTCM; x++) - { - response &= theGNSS->addCfgValset(ubxMessages[firstRTCMRecord + x].msgConfigKey - 1, - settings.ubxMessageRatesBase[x]); // UBLOX_CFG UART1 - 1 = I2C - response &= theGNSS->addCfgValset(ubxMessages[firstRTCMRecord + x].msgConfigKey, - settings.ubxMessageRatesBase[x]); // UBLOX_CFG UART1 - - // Disable messages on SPI - response &= theGNSS->addCfgValset(ubxMessages[firstRTCMRecord + x].msgConfigKey + 3, - 0); // UBLOX_CFG UART1 + 3 = SPI - } - - // Update message rates for UART2 and USB - for (int x = 0; x < MAX_UBX_MSG_RTCM; x++) - { - response &= theGNSS->addCfgValset(ubxMessages[firstRTCMRecord + x].msgConfigKey + 1, - settings.ubxMessageRatesBase[x]); // UBLOX_CFG UART1 + 1 = UART2 - response &= theGNSS->addCfgValset(ubxMessages[firstRTCMRecord + x].msgConfigKey + 2, - settings.ubxMessageRatesBase[x]); // UBLOX_CFG UART1 + 2 = USB - } - - response &= theGNSS->addCfgValset(UBLOX_CFG_NAVSPG_INFIL_MINELEV, settings.minElev); // Set minimum elevation - - response &= theGNSS->sendCfgValset(); // Closing value - - if (response) - success = true; - } - - if (!success) - systemPrintln("Base config fail"); - - return (success); -} - -// Slightly modified method for restarting survey-in from: -// https://portal.u-blox.com/s/question/0D52p00009IsVoMCAV/restarting-surveyin-on-an-f9p -bool zedSurveyInReset() -{ - bool response = true; - - // Disable survey-in mode - response &= theGNSS->setVal8(UBLOX_CFG_TMODE_MODE, 0); - delay(1000); - - // Enable Survey in with bogus values - response &= theGNSS->newCfgValset(); - response &= theGNSS->addCfgValset(UBLOX_CFG_TMODE_MODE, 1); // Survey-in enable - response &= theGNSS->addCfgValset(UBLOX_CFG_TMODE_SVIN_ACC_LIMIT, 40 * 10000); // 40.0m - response &= theGNSS->addCfgValset(UBLOX_CFG_TMODE_SVIN_MIN_DUR, 1000); // 1000s - response &= theGNSS->sendCfgValset(); - delay(1000); - - // Disable survey-in mode - response &= theGNSS->setVal8(UBLOX_CFG_TMODE_MODE, 0); - - if (response == false) - return (response); - - // Wait until active and valid becomes false - long maxTime = 5000; - long startTime = millis(); - while (theGNSS->getSurveyInActive(100) == true || theGNSS->getSurveyInValid(100) == true) - { - delay(100); - if (millis() - startTime > maxTime) - return (false); // Reset of survey failed - } - - return (true); -} - -// Start survey -// The ZED-F9P is slightly different than the NEO-M8P. See the Integration manual 3.5.8 for more info. -bool zedSurveyInStart() -{ - theGNSS->setVal8(UBLOX_CFG_TMODE_MODE, 0); // Disable survey-in mode - delay(100); - - bool needSurveyReset = false; - if (theGNSS->getSurveyInActive(100) == true) - needSurveyReset = true; - if (theGNSS->getSurveyInValid(100) == true) - needSurveyReset = true; - - if (needSurveyReset == true) - { - systemPrintln("Resetting survey"); - - if (zedSurveyInReset() == false) - { - systemPrintln("Survey reset failed - attempt 1/3"); - if (zedSurveyInReset() == false) - { - systemPrintln("Survey reset failed - attempt 2/3"); - if (zedSurveyInReset() == false) - { - systemPrintln("Survey reset failed - attempt 3/3"); - } - } - } - } - - bool response = true; - response &= theGNSS->setVal8(UBLOX_CFG_TMODE_MODE, 1); // Survey-in enable - response &= theGNSS->setVal32(UBLOX_CFG_TMODE_SVIN_ACC_LIMIT, settings.observationPositionAccuracy * 10000); - response &= theGNSS->setVal32(UBLOX_CFG_TMODE_SVIN_MIN_DUR, settings.observationSeconds); - - if (response == false) - { - systemPrintln("Survey start failed"); - return (false); - } - - systemPrintf("Survey started. This will run until %d seconds have passed and less than %0.03f meter accuracy is " - "achieved.\r\n", - settings.observationSeconds, settings.observationPositionAccuracy); - - // Wait until active becomes true - long maxTime = 5000; - long startTime = millis(); - while (theGNSS->getSurveyInActive(100) == false) - { - delay(100); - if (millis() - startTime > maxTime) - return (false); // Reset of survey failed - } - - return (true); -} - -// Start the base using fixed coordinates -bool zedFixedBaseStart() -{ - bool response = true; - - if (settings.fixedBaseCoordinateType == COORD_TYPE_ECEF) - { - // Break ECEF into main and high precision parts - // The type casting should not effect rounding of original double cast coordinate - long majorEcefX = floor((settings.fixedEcefX * 100.0) + 0.5); - long minorEcefX = floor((((settings.fixedEcefX * 100.0) - majorEcefX) * 100.0) + 0.5); - long majorEcefY = floor((settings.fixedEcefY * 100) + 0.5); - long minorEcefY = floor((((settings.fixedEcefY * 100.0) - majorEcefY) * 100.0) + 0.5); - long majorEcefZ = floor((settings.fixedEcefZ * 100) + 0.5); - long minorEcefZ = floor((((settings.fixedEcefZ * 100.0) - majorEcefZ) * 100.0) + 0.5); - - // systemPrintf("fixedEcefY (should be -4716808.5807): %0.04f\r\n", settings.fixedEcefY); - // systemPrintf("major (should be -471680858): %ld\r\n", majorEcefY); - // systemPrintf("minor (should be -7): %ld\r\n", minorEcefY); - - // Units are cm with a high precision extension so -1234.5678 should be called: (-123456, -78) - //-1280208.308,-4716803.847,4086665.811 is SparkFun HQ so... - - response &= theGNSS->newCfgValset(); - response &= theGNSS->addCfgValset(UBLOX_CFG_TMODE_MODE, 2); // Fixed - response &= theGNSS->addCfgValset(UBLOX_CFG_TMODE_POS_TYPE, 0); // Position in ECEF - response &= theGNSS->addCfgValset(UBLOX_CFG_TMODE_ECEF_X, majorEcefX); - response &= theGNSS->addCfgValset(UBLOX_CFG_TMODE_ECEF_X_HP, minorEcefX); - response &= theGNSS->addCfgValset(UBLOX_CFG_TMODE_ECEF_Y, majorEcefY); - response &= theGNSS->addCfgValset(UBLOX_CFG_TMODE_ECEF_Y_HP, minorEcefY); - response &= theGNSS->addCfgValset(UBLOX_CFG_TMODE_ECEF_Z, majorEcefZ); - response &= theGNSS->addCfgValset(UBLOX_CFG_TMODE_ECEF_Z_HP, minorEcefZ); - response &= theGNSS->sendCfgValset(); - } - else if (settings.fixedBaseCoordinateType == COORD_TYPE_GEODETIC) - { - // Add height of instrument (HI) to fixed altitude - // https://www.e-education.psu.edu/geog862/node/1853 - // For example, if HAE is at 100.0m, + 2m stick + 73mm ARP = 102.073 - float totalFixedAltitude = - settings.fixedAltitude + ((settings.antennaHeight_mm + settings.antennaPhaseCenter_mm) / 1000.0); - - // Break coordinates into main and high precision parts - // The type casting should not effect rounding of original double cast coordinate - int64_t majorLat = settings.fixedLat * 10000000; - int64_t minorLat = ((settings.fixedLat * 10000000) - majorLat) * 100; - int64_t majorLong = settings.fixedLong * 10000000; - int64_t minorLong = ((settings.fixedLong * 10000000) - majorLong) * 100; - int32_t majorAlt = totalFixedAltitude * 100; - int32_t minorAlt = ((totalFixedAltitude * 100) - majorAlt) * 100; - - // systemPrintf("fixedLong (should be -105.184774720): %0.09f\r\n", settings.fixedLong); - // systemPrintf("major (should be -1051847747): %lld\r\n", majorLat); - // systemPrintf("minor (should be -20): %lld\r\n", minorLat); - // - // systemPrintf("fixedLat (should be 40.090335429): %0.09f\r\n", settings.fixedLat); - // systemPrintf("major (should be 400903354): %lld\r\n", majorLong); - // systemPrintf("minor (should be 29): %lld\r\n", minorLong); - // - // systemPrintf("fixedAlt (should be 1560.2284): %0.04f\r\n", settings.fixedAltitude); - // systemPrintf("major (should be 156022): %ld\r\n", majorAlt); - // systemPrintf("minor (should be 84): %ld\r\n", minorAlt); - - response &= theGNSS->newCfgValset(); - response &= theGNSS->addCfgValset(UBLOX_CFG_TMODE_MODE, 2); // Fixed - response &= theGNSS->addCfgValset(UBLOX_CFG_TMODE_POS_TYPE, 1); // Position in LLH - response &= theGNSS->addCfgValset(UBLOX_CFG_TMODE_LAT, majorLat); - response &= theGNSS->addCfgValset(UBLOX_CFG_TMODE_LAT_HP, minorLat); - response &= theGNSS->addCfgValset(UBLOX_CFG_TMODE_LON, majorLong); - response &= theGNSS->addCfgValset(UBLOX_CFG_TMODE_LON_HP, minorLong); - response &= theGNSS->addCfgValset(UBLOX_CFG_TMODE_HEIGHT, majorAlt); - response &= theGNSS->addCfgValset(UBLOX_CFG_TMODE_HEIGHT_HP, minorAlt); - response &= theGNSS->sendCfgValset(); - } - - return (response); -} - -// Setup the timepulse output on the PPS pin for external triggering -// Setup TM2 time stamp input as need -bool zedBeginExternalEvent() -{ - if (online.gnss == false) - return (false); - - // If our settings haven't changed, trust ZED's settings - if (settings.updateGNSSSettings == false) - { - log_d("Skipping ZED Trigger configuration"); - return (true); - } - - if (settings.dataPortChannel != MUX_PPS_EVENTTRIGGER) - return (true); // No need to configure PPS if port is not selected - - bool response = true; - - if (settings.enableExternalHardwareEventLogging == true) - { - theGNSS->setAutoTIMTM2callbackPtr( - &eventTriggerReceived); // Enable automatic TIM TM2 messages with callback to eventTriggerReceived - } - else - theGNSS->setAutoTIMTM2callbackPtr(nullptr); - - return (response); -} - -// Setup the timepulse output on the PPS pin for external triggering -bool zedBeginPPS() -{ - if (online.gnss == false) - return (false); - - // If our settings haven't changed, trust ZED's settings - if (settings.updateGNSSSettings == false) - { - systemPrintln("Skipping ZED Trigger configuration"); - return (true); - } - - if (settings.dataPortChannel != MUX_PPS_EVENTTRIGGER) - return (true); // No need to configure PPS if port is not selected - - bool response = true; - - response &= theGNSS->newCfgValset(); - response &= theGNSS->addCfgValset(UBLOX_CFG_TP_PULSE_DEF, 0); // Time pulse definition is a period (in us) - response &= theGNSS->addCfgValset(UBLOX_CFG_TP_PULSE_LENGTH_DEF, 1); // Define timepulse by length (not ratio) - response &= - theGNSS->addCfgValset(UBLOX_CFG_TP_USE_LOCKED_TP1, - 1); // Use CFG-TP-PERIOD_LOCK_TP1 and CFG-TP-LEN_LOCK_TP1 as soon as GNSS time is valid - response &= theGNSS->addCfgValset(UBLOX_CFG_TP_TP1_ENA, settings.enableExternalPulse); // Enable/disable timepulse - response &= - theGNSS->addCfgValset(UBLOX_CFG_TP_POL_TP1, settings.externalPulsePolarity); // 0 = falling, 1 = rising edge - - // While the module is _locking_ to GNSS time, turn off pulse - response &= theGNSS->addCfgValset(UBLOX_CFG_TP_PERIOD_TP1, 1000000); // Set the period between pulses in us - response &= theGNSS->addCfgValset(UBLOX_CFG_TP_LEN_TP1, 0); // Set the pulse length in us - - // When the module is _locked_ to GNSS time, make it generate 1Hz (Default is 100ms high, 900ms low) - response &= theGNSS->addCfgValset(UBLOX_CFG_TP_PERIOD_LOCK_TP1, - settings.externalPulseTimeBetweenPulse_us); // Set the period between pulses is us - response &= - theGNSS->addCfgValset(UBLOX_CFG_TP_LEN_LOCK_TP1, settings.externalPulseLength_us); // Set the pulse length in us - response &= theGNSS->sendCfgValset(); - - if (response == false) - systemPrintln("beginExternalTriggers config failed"); - - return (response); -} - -// Enable data output from the NEO -bool zedEnableLBandCommunication() -{ - /* - Paul's Notes on (NEO-D9S) L-Band: - - Torch will receive PointPerfect SPARTN via IP, run it through the PPL, and feed RTCM to the UM980. No L-Band... - - The EVK has ZED-F9P and NEO-D9S. But there are two versions of the PCB: - v1.1 PCB : - Both ZED and NEO are on the i2c_0 I2C bus (the OLED is on i2c_1) - ZED UART1 is connected to the ESP32 (pins 25 and 33) only - ZED UART2 is connected to the I/O connector only - NEO UART1 is connected to test points only - NEO UART2 is not connected - v1.0 PCB (the one we are currently using for code development) : - Both ZED and NEO are on the i2c_0 I2C bus - ZED UART1 is connected to NEO UART1 only - not to ESP32 (Big mistake! Makes BT and Logging much more - complicated...) ZED UART2 is connected to the I/O connector only NEO UART2 is not connected - - Facet v2 hasn't been built yet. The v2.01 PCB probably won't get built as it needs the new soft power switch. - When v2.10 (?) gets built : - Both ZED and NEO are on the I2C bus - ZED UART1 is connected to the ESP32 (pins 14 and 13) and also to the DATA connector via the Mux (output - only) ZED UART2 is connected to the RADIO connector only NEO UART1 is not connected NEO UART2 TX is connected to - ESP32 pin 4. If the ESP32 has a UART spare - and it probably does - the PMP data can go over this connection and - avoid having double-PMP traffic on I2C. Neat huh?! - - Facet mosaic v1.0 PCB has been built, but needs the new soft power switch and some other minor mods. - X5 COM1 is connected to the ESP32 (pins 13 and 14) - RTCM from PPL, NMEA and RTCM to PPL and Bluetooth - X5 COM2 is connected to the RADIO connector only - X5 COM3 is connected to the DATA connector via the Mux (I/O) - X5 COM4 is connected to the ESP32 (pins 4 and 25) - raw L-Band to PPL, control from ESP32 to X5 ? - - So, what does all of this mean? - EVK v1.0 supports direct NEO to ZED UART communication, but V1.1 will not. There is no point in supporting it - here. Facet v2 can pipe NEO UART PMP data to the ZED (over I2C or maybe UART), but the hardware doesn't exist yet - so there is no point in adding that feature yet... TODO. So, right now, we should assume NEO PMP data will arrive - via I2C, and will get pushed to the ZED via I2C if the corrections priority permits. Deleting: - useI2cForLbandCorrections, useI2cForLbandCorrectionsConfigured and rtcmTimeoutBeforeUsingLBand_s - */ - - bool response = true; - -#ifdef COMPILE_L_BAND - - response &= theGNSS->setRXMCORcallbackPtr( - &checkRXMCOR); // Enable callback to check if the PMP data is being decrypted successfully - - if (present.lband_neo == true) - { - response &= i2cLBand.setRXMPMPmessageCallbackPtr(&pushRXMPMP); // Enable PMP callback to push raw PMP over I2C - - response &= i2cLBand.newCfgValset(); - - response &= i2cLBand.addCfgValset(UBLOX_CFG_MSGOUT_UBX_RXM_PMP_I2C, 1); // Enable UBX-RXM-PMP on NEO's I2C port - - response &= i2cLBand.addCfgValset(UBLOX_CFG_UART1OUTPROT_UBX, 0); // Disable UBX output on NEO's UART1 - - response &= i2cLBand.addCfgValset(UBLOX_CFG_MSGOUT_UBX_RXM_PMP_UART1, 0); // Disable UBX-RXM-PMP on NEO's UART1 - - // TODO: change this as needed for Facet v2 - response &= i2cLBand.addCfgValset(UBLOX_CFG_UART2OUTPROT_UBX, 0); // Disable UBX output on NEO's UART2 - - // TODO: change this as needed for Facet v2 - response &= i2cLBand.addCfgValset(UBLOX_CFG_MSGOUT_UBX_RXM_PMP_UART2, 0); // Disable UBX-RXM-PMP on NEO's UART2 - - response &= i2cLBand.sendCfgValset(); - } - else - { - systemPrintln("zedEnableLBandCorrections: Unknown platform"); - return (false); - } - -#endif - - return (response); -} - -// Disable data output from the NEO -bool zedDisableLBandCommunication() -{ - bool response = true; - -#ifdef COMPILE_L_BAND - - response &= theGNSS->setRXMCORcallbackPtr( - nullptr); // Disable callback to check if the PMP data is being decrypted successfully - - response &= i2cLBand.setRXMPMPmessageCallbackPtr(nullptr); // Disable PMP callback no matter the platform - - if (present.lband_neo == true) - { - response &= i2cLBand.newCfgValset(); - - response &= - i2cLBand.addCfgValset(UBLOX_CFG_MSGOUT_UBX_RXM_PMP_I2C, 0); // Disable UBX-RXM-PMP from NEO's I2C port - - // TODO: change this as needed for Facet v2 - response &= i2cLBand.addCfgValset(UBLOX_CFG_UART2OUTPROT_UBX, 0); // Disable UBX output from NEO's UART2 - - // TODO: change this as needed for Facet v2 - response &= i2cLBand.addCfgValset(UBLOX_CFG_MSGOUT_UBX_RXM_PMP_UART2, 0); // Disable UBX-RXM-PMP on NEO's UART2 - - response &= i2cLBand.sendCfgValset(); - } - else - { - systemPrintln("zedEnableLBandCorrections: Unknown platform"); - return (false); - } - -#endif - - return (response); -} - -// Given the number of seconds between desired solution reports, determine measurementRateMs and navigationRate -// measurementRateS > 25 & <= 65535 -// navigationRate >= 1 && <= 127 -// We give preference to limiting a measurementRate to 30 or below due to reported problems with measRates above 30. -bool zedSetRate(double secondsBetweenSolutions) -{ - uint16_t measRate = 0; // Calculate these locally and then attempt to apply them to ZED at completion - uint16_t navRate = 0; - - // If we have more than an hour between readings, increase mesaurementRate to near max of 65,535 - if (secondsBetweenSolutions > 3600.0) - { - measRate = 65000; - } - - // If we have more than 30s, but less than 3600s between readings, use 30s measurement rate - else if (secondsBetweenSolutions > 30.0) - { - measRate = 30000; - } - - // User wants measurements less than 30s (most common), set measRate to match user request - // This will make navRate = 1. - else - { - measRate = secondsBetweenSolutions * 1000.0; - } - - navRate = secondsBetweenSolutions * 1000.0 / measRate; // Set navRate to nearest int value - measRate = secondsBetweenSolutions * 1000.0 / navRate; // Adjust measurement rate to match actual navRate - - // systemPrintf("measurementRate / navRate: %d / %d\r\n", measRate, navRate); - - bool response = true; - response &= theGNSS->newCfgValset(); - response &= theGNSS->addCfgValset(UBLOX_CFG_RATE_MEAS, measRate); - response &= theGNSS->addCfgValset(UBLOX_CFG_RATE_NAV, navRate); - - int gsvRecordNumber = zedGetMessageNumberByName("NMEA_GSV"); - - // If enabled, adjust GSV NMEA to be reported at 1Hz to avoid swamping SPP connection - if (settings.ubxMessageRates[gsvRecordNumber] > 0) - { - float measurementFrequency = (1000.0 / measRate) / navRate; - if (measurementFrequency < 1.0) - measurementFrequency = 1.0; - - log_d("Adjusting GSV setting to %f", measurementFrequency); - - zedSetMessageRateByName("NMEA_GSV", measurementFrequency); // Update GSV setting in file - response &= theGNSS->addCfgValset(ubxMessages[gsvRecordNumber].msgConfigKey, - settings.ubxMessageRates[gsvRecordNumber]); // Update rate on module - } - - response &= theGNSS->sendCfgValset(); // Closing value - max 4 pairs - - // If we successfully set rates, only then record to settings - if (response == true) - { - settings.measurementRateMs = measRate; - settings.navigationRate = navRate; - } - else - { - systemPrintln("Failed to set measurement and navigation rates"); - return (false); - } - - return (true); -} - -// Returns the seconds between measurements -double zedGetRateS() -{ - // Because we may be in base mode, do not get freq from module, use settings instead - float measurementFrequency = (1000.0 / settings.measurementRateMs) / settings.navigationRate; - double measurementRateS = 1.0 / measurementFrequency; // 1 / 4Hz = 0.25s - - return (measurementRateS); -} - -void zedSetElevation(uint8_t elevationDegrees) -{ - theGNSS->setVal8(UBLOX_CFG_NAVSPG_INFIL_MINELEV, elevationDegrees); // Set minimum elevation -} - -void zedSetMinCno(uint8_t cnoValue) -{ - theGNSS->setVal8(UBLOX_CFG_NAVSPG_INFIL_MINCNO, cnoValue); -} - -double zedGetLatitude() -{ - return (zedLatitude); -} - -double zedGetLongitude() -{ - return (zedLongitude); -} - -double zedGetAltitude() -{ - return (zedAltitude); -} - -float zedGetHorizontalAccuracy() -{ - return (zedHorizontalAccuracy); -} - -uint8_t zedGetSatellitesInView() -{ - return (zedSatellitesInView); -} - -// Return full year, ie 2023, not 23. -uint16_t zedGetYear() -{ - return (zedYear); -} -uint8_t zedGetMonth() -{ - return (zedMonth); -} -uint8_t zedGetDay() -{ - return (zedDay); -} -uint8_t zedGetHour() -{ - return (zedHour); -} -uint8_t zedGetMinute() -{ - return (zedMinute); -} -uint8_t zedGetSecond() -{ - return (zedSecond); -} -uint8_t zedGetMillisecond() -{ - return (zedMillisecond); -} -uint8_t zedGetNanosecond() -{ - return (zedNanosecond); -} - -uint8_t zedGetFixType() -{ - return (zedFixType); -} -uint8_t zedGetCarrierSolution() -{ - return (zedCarrierSolution); -} - -bool zedIsValidDate() -{ - return (zedValidDate); -} -bool zedIsValidTime() -{ - return (zedValidTime); -} -bool zedIsConfirmedDate() -{ - return (zedConfirmedDate); -} -bool zedIsConfirmedTime() -{ - return (zedConfirmedTime); -} -bool zedIsFullyResolved() -{ - return (zedFullyResolved); -} -uint32_t zedGetTimeAccuracy() -{ - return (zedTAcc); -} - -// Return the number of milliseconds since data was updated -uint16_t zedFixAgeMilliseconds() -{ - return (millis() - zedPvtArrivalMillis); -} - -// Print the module type and firmware version -void zedPrintInfo() -{ - systemPrintf("ZED-F9P firmware: %s\r\n", gnssFirmwareVersion); -} - -void zedFactoryReset() -{ - theGNSS->factoryDefault(); // Reset everything: baud rate, I2C address, update rate, everything. And save to BBR. - theGNSS->saveConfiguration(); - theGNSS->hardReset(); // Perform a reset leading to a cold start (zero info start-up) -} - -void zedSaveConfiguration() -{ - theGNSS->saveConfiguration(); // Save the current settings to flash and BBR on the ZED-F9P -} - -void zedEnableDebugging() -{ - theGNSS->enableDebugging(Serial, true); // Enable only the critical debug messages over Serial -} -void zedDisableDebugging() -{ - theGNSS->disableDebugging(); -} - -void zedSetTalkerGNGGA() -{ - theGNSS->setVal8(UBLOX_CFG_NMEA_MAINTALKERID, 3); // Return talker ID to GNGGA after NTRIP Client set to GPGGA - theGNSS->setNMEAGPGGAcallbackPtr(nullptr); // Remove callback -} -void zedEnableGgaForNtrip() -{ - // Set the Main Talker ID to "GP". The NMEA GGA messages will be GPGGA instead of GNGGA - theGNSS->setVal8(UBLOX_CFG_NMEA_MAINTALKERID, 1); - theGNSS->setNMEAGPGGAcallbackPtr(&pushGPGGA); // Set up the callback for GPGGA - - float measurementFrequency = (1000.0 / settings.measurementRateMs) / settings.navigationRate; - if (measurementFrequency < 0.2) - measurementFrequency = 0.2; // 0.2Hz * 5 = 1 measurement every 5 seconds - log_d("Adjusting GGA setting to %f", measurementFrequency); - theGNSS->setVal8(UBLOX_CFG_MSGOUT_NMEA_ID_GGA_I2C, - measurementFrequency); // Enable GGA over I2C. Tell the module to output GGA every second -} - -// Enable all the valid messages for this platform -// There are many messages so split into batches. VALSET is limited to 64 max per batch -// Uses dummy newCfg and sendCfg values to be sure we open/close a complete set -bool zedSetMessages(int maxRetries) -{ - bool success = false; - int tryNo = -1; - - // Try up to maxRetries times to configure the messages - // This corrects occasional failures seen on the Reference Station where the GNSS is connected via SPI - // instead of I2C and UART1. I believe the SETVAL ACK is occasionally missed due to the level of messages being - // processed. - while ((++tryNo < maxRetries) && !success) - { - bool response = true; - int messageNumber = 0; - - while (messageNumber < MAX_UBX_MSG) - { - response &= theGNSS->newCfgValset(); - - do - { - if (messageSupported(messageNumber) == true) - { - uint8_t rate = settings.ubxMessageRates[messageNumber]; - - response &= theGNSS->addCfgValset(ubxMessages[messageNumber].msgConfigKey, rate); - } - messageNumber++; - } while (((messageNumber % 43) < 42) && - (messageNumber < MAX_UBX_MSG)); // Limit 1st batch to 42. Batches after that will be (up to) 43 in - // size. It's a HHGTTG thing. - - if (theGNSS->sendCfgValset() == false) - { - log_d("sendCfg failed at messageNumber %d %s. Try %d of %d.", messageNumber - 1, - (messageNumber - 1) < MAX_UBX_MSG ? ubxMessages[messageNumber - 1].msgTextName : "", tryNo + 1, - maxRetries); - response &= false; // If any one of the Valset fails, report failure overall - } - } - - if (response) - success = true; - } - - return (success); -} - -// Enable all the valid messages for this platform over the USB port -// Add 2 to every UART1 key. This is brittle and non-perfect, but works. -bool zedSetMessagesUsb(int maxRetries) -{ - bool success = false; - int tryNo = -1; - - // Try up to maxRetries times to configure the messages - // This corrects occasional failures seen on the Reference Station where the GNSS is connected via SPI - // instead of I2C and UART1. I believe the SETVAL ACK is occasionally missed due to the level of messages being - // processed. - while ((++tryNo < maxRetries) && !success) - { - bool response = true; - int messageNumber = 0; - - while (messageNumber < MAX_UBX_MSG) - { - response &= theGNSS->newCfgValset(); - - do - { - if (messageSupported(messageNumber) == true) - response &= theGNSS->addCfgValset(ubxMessages[messageNumber].msgConfigKey + 2, - settings.ubxMessageRates[messageNumber]); - messageNumber++; - } while (((messageNumber % 43) < 42) && - (messageNumber < MAX_UBX_MSG)); // Limit 1st batch to 42. Batches after that will be (up to) 43 in - // size. It's a HHGTTG thing. - - response &= theGNSS->sendCfgValset(); - } - - if (response) - success = true; - } - - return (success); -} - -// Enable all the valid constellations and bands for this platform -// Band support varies between platforms and firmware versions -// We open/close a complete set if sendCompleteBatch = true -// 19 messages -bool zedSetConstellations(bool sendCompleteBatch) -{ - bool response = true; - - if (sendCompleteBatch) - response &= theGNSS->newCfgValset(); - - // GPS - int gnssIndex = ubxConstellationIDToIndex(SFE_UBLOX_GNSS_ID_GPS); - bool enableMe = settings.ubxConstellations[gnssIndex].enabled; - response &= theGNSS->addCfgValset(settings.ubxConstellations[gnssIndex].configKey, enableMe); - response &= theGNSS->addCfgValset(UBLOX_CFG_SIGNAL_GPS_L1CA_ENA, enableMe); - response &= theGNSS->addCfgValset(UBLOX_CFG_SIGNAL_GPS_L2C_ENA, enableMe); - - // SBAS - // v1.12 ZED-F9P firmware does not allow for SBAS control - // Also, if we can't identify the version (99), skip SBAS enable - if ((gnssFirmwareVersionInt == 112) || (gnssFirmwareVersionInt == 99)) - { - // Skip - } - else - { - gnssIndex = ubxConstellationIDToIndex(SFE_UBLOX_GNSS_ID_SBAS); - enableMe = settings.ubxConstellations[gnssIndex].enabled; - response &= theGNSS->addCfgValset(settings.ubxConstellations[gnssIndex].configKey, enableMe); - response &= theGNSS->addCfgValset(UBLOX_CFG_SIGNAL_SBAS_L1CA_ENA, enableMe); - } - - // GAL - gnssIndex = ubxConstellationIDToIndex(SFE_UBLOX_GNSS_ID_GALILEO); - enableMe = settings.ubxConstellations[gnssIndex].enabled; - response &= - theGNSS->addCfgValset(settings.ubxConstellations[gnssIndex].configKey, enableMe); - response &= theGNSS->addCfgValset(UBLOX_CFG_SIGNAL_GAL_E1_ENA, enableMe); - response &= theGNSS->addCfgValset(UBLOX_CFG_SIGNAL_GAL_E5B_ENA, enableMe); - - // BDS - gnssIndex = ubxConstellationIDToIndex(SFE_UBLOX_GNSS_ID_BEIDOU); - enableMe = settings.ubxConstellations[gnssIndex].enabled; - response &= - theGNSS->addCfgValset(settings.ubxConstellations[gnssIndex].configKey, enableMe); - response &= theGNSS->addCfgValset(UBLOX_CFG_SIGNAL_BDS_B1_ENA, enableMe); - response &= theGNSS->addCfgValset(UBLOX_CFG_SIGNAL_BDS_B2_ENA, enableMe); - - // QZSS - gnssIndex = ubxConstellationIDToIndex(SFE_UBLOX_GNSS_ID_QZSS); - enableMe = settings.ubxConstellations[gnssIndex].enabled; - response &= - theGNSS->addCfgValset(settings.ubxConstellations[gnssIndex].configKey, enableMe); - response &= theGNSS->addCfgValset(UBLOX_CFG_SIGNAL_QZSS_L1CA_ENA, enableMe); - // UBLOX_CFG_SIGNAL_QZSS_L1S_ENA not supported on F9R in v1.21 and below - response &= theGNSS->addCfgValset(UBLOX_CFG_SIGNAL_QZSS_L1S_ENA, enableMe); - response &= theGNSS->addCfgValset(UBLOX_CFG_SIGNAL_QZSS_L2C_ENA, enableMe); - - // GLO - gnssIndex = ubxConstellationIDToIndex(SFE_UBLOX_GNSS_ID_GLONASS); - enableMe = settings.ubxConstellations[gnssIndex].enabled; - response &= - theGNSS->addCfgValset(settings.ubxConstellations[gnssIndex].configKey, enableMe); - response &= theGNSS->addCfgValset(UBLOX_CFG_SIGNAL_GLO_L1_ENA, enableMe); - response &= theGNSS->addCfgValset(UBLOX_CFG_SIGNAL_GLO_L2_ENA, enableMe); - - if (sendCompleteBatch) - response &= theGNSS->sendCfgValset(); - - return (response); -} - -uint16_t zedFileBufferAvailable() -{ - return (theGNSS->fileBufferAvailable()); -} - -uint16_t zedRtcmBufferAvailable() -{ - return (theGNSS->rtcmBufferAvailable()); -} - -uint16_t zedRtcmRead(uint8_t *rtcmBuffer, int rtcmBytesToRead) -{ - return (theGNSS->extractRTCMBufferData(rtcmBuffer, rtcmBytesToRead)); -} - -uint16_t zedExtractFileBufferData(uint8_t *fileBuffer, int fileBytesToRead) -{ - theGNSS->extractFileBufferData(fileBuffer, - fileBytesToRead); // TODO Does extractFileBufferData not return the bytes read? - return (1); -} - -// Query GNSS for current leap seconds -uint8_t zedGetLeapSeconds() -{ - sfe_ublox_ls_src_e leapSecSource; - leapSeconds = theGNSS->getCurrentLeapSeconds(leapSecSource); - return (leapSeconds); -} - -// If we have decryption keys, configure module -// Note: don't check online.lband_neo here. We could be using ip corrections -void zedApplyPointPerfectKeys() -{ - if (online.gnss == false) - { - if (settings.debugCorrections == true) - systemPrintln("ZED-F9P not available"); - return; - } - - // NEO-D9S encrypted PMP messages are only supported on ZED-F9P firmware v1.30 and above - if (gnssFirmwareVersionInt < 130) - { - systemPrintln("Error: PointPerfect corrections currently supported by ZED-F9P firmware v1.30 and above. " - "Please upgrade your ZED firmware: " - "https://learn.sparkfun.com/tutorials/how-to-upgrade-firmware-of-a-u-blox-gnss-receiver"); - return; - } - - if (strlen(settings.pointPerfectNextKey) > 0) - { - const uint8_t currentKeyLengthBytes = 16; - const uint8_t nextKeyLengthBytes = 16; - - uint16_t currentKeyGPSWeek; - uint32_t currentKeyGPSToW; - long long epoch = thingstreamEpochToGPSEpoch(settings.pointPerfectCurrentKeyStart); - epochToWeekToW(epoch, ¤tKeyGPSWeek, ¤tKeyGPSToW); - - uint16_t nextKeyGPSWeek; - uint32_t nextKeyGPSToW; - epoch = thingstreamEpochToGPSEpoch(settings.pointPerfectNextKeyStart); - epochToWeekToW(epoch, &nextKeyGPSWeek, &nextKeyGPSToW); - - // If we are on a L-Band-only or L-Band+IP, set the SOURCE to 1 (L-Band) - // Else set the SOURCE to 0 (IP) - // If we are on L-Band+IP and IP corrections start to arrive, the corrections - // priority code will change SOURCE to match - if (strstr(settings.pointPerfectKeyDistributionTopic, "/Lb") != nullptr) - { - updateZEDCorrectionsSource(1); // Set SOURCE to 1 (L-Band) if needed - } - else - { - updateZEDCorrectionsSource(0); // Set SOURCE to 0 (IP) if needed - } - - theGNSS->setVal8(UBLOX_CFG_MSGOUT_UBX_RXM_COR_I2C, 1); // Enable UBX-RXM-COR messages on I2C - - theGNSS->setVal8(UBLOX_CFG_NAVHPG_DGNSSMODE, - 3); // Set the differential mode - ambiguities are fixed whenever possible - - bool response = theGNSS->setDynamicSPARTNKeys(currentKeyLengthBytes, currentKeyGPSWeek, currentKeyGPSToW, - settings.pointPerfectCurrentKey, nextKeyLengthBytes, - nextKeyGPSWeek, nextKeyGPSToW, settings.pointPerfectNextKey); - - if (response == false) - systemPrintln("setDynamicSPARTNKeys failed"); - else - { - if (settings.debugCorrections == true) - systemPrintln("PointPerfect keys applied"); - online.lbandCorrections = true; - } - } - else - { - if (settings.debugCorrections == true) - systemPrintln("No PointPerfect keys available"); - } -} - -void updateZEDCorrectionsSource(uint8_t source) -{ - if (!online.gnss) - return; - - if (!present.gnss_zedf9p) - return; - - if (zedCorrectionsSource == source) - return; - - // This is important. Retry if needed - int retries = 0; - while ((!theGNSS->setVal8(UBLOX_CFG_SPARTN_USE_SOURCE, source)) && (retries < 3)) - retries++; - if (retries < 3) - { - zedCorrectionsSource = source; - if (settings.debugCorrections == true && !inMainMenu) - systemPrintf("ZED UBLOX_CFG_SPARTN_USE_SOURCE changed to %d\r\n", source); - } - else - systemPrintf("updateZEDCorrectionsSource(%d) failed!\r\n", source); -} - -uint8_t zedGetActiveMessageCount() -{ - uint8_t count = 0; - - for (int x = 0; x < MAX_UBX_MSG; x++) - if (settings.ubxMessageRates[x] > 0) - count++; - return (count); -} - -// Control the messages that get broadcast over Bluetooth and logged (if enabled) -void zedMenuMessages() -{ - while (1) - { - systemPrintln(); - systemPrintln("Menu: GNSS Messages"); - - systemPrintf("Active messages: %d\r\n", gnssGetActiveMessageCount()); - - systemPrintln("1) Set NMEA Messages"); - systemPrintln("2) Set RTCM Messages"); - systemPrintln("3) Set RXM Messages"); - systemPrintln("4) Set NAV Messages"); - systemPrintln("5) Set NAV2 Messages"); - systemPrintln("6) Set NMEA NAV2 Messages"); - systemPrintln("7) Set MON Messages"); - systemPrintln("8) Set TIM Messages"); - systemPrintln("9) Set PUBX Messages"); - - systemPrintln("10) Reset to Surveying Defaults (NMEAx5)"); - systemPrintln("11) Reset to PPP Logging Defaults (NMEAx5 + RXMx2)"); - systemPrintln("12) Turn off all messages"); - systemPrintln("13) Turn on all messages"); - - systemPrintln("x) Exit"); - - int incoming = getUserInputNumber(); // Returns EXIT, TIMEOUT, or long - - if (incoming == 1) - zedMenuMessagesSubtype(settings.ubxMessageRates, - "NMEA_"); // The following _ avoids listing NMEANAV2 messages - else if (incoming == 2) - zedMenuMessagesSubtype(settings.ubxMessageRates, "RTCM"); - else if (incoming == 3) - zedMenuMessagesSubtype(settings.ubxMessageRates, "RXM"); - else if (incoming == 4) - zedMenuMessagesSubtype(settings.ubxMessageRates, "NAV_"); // The following _ avoids listing NAV2 messages - else if (incoming == 5) - zedMenuMessagesSubtype(settings.ubxMessageRates, "NAV2"); - else if (incoming == 6) - zedMenuMessagesSubtype(settings.ubxMessageRates, "NMEANAV2"); - else if (incoming == 7) - zedMenuMessagesSubtype(settings.ubxMessageRates, "MON"); - else if (incoming == 8) - zedMenuMessagesSubtype(settings.ubxMessageRates, "TIM"); - else if (incoming == 9) - zedMenuMessagesSubtype(settings.ubxMessageRates, "PUBX"); - else if (incoming == 10) - { - setGNSSMessageRates(settings.ubxMessageRates, 0); // Turn off all messages - zedSetMessageRateByName("NMEA_GGA", 1); - zedSetMessageRateByName("NMEA_GSA", 1); - zedSetMessageRateByName("NMEA_GST", 1); - - // We want GSV NMEA to be reported at 1Hz to avoid swamping SPP connection - float measurementFrequency = (1000.0 / settings.measurementRateMs) / settings.navigationRate; - if (measurementFrequency < 1.0) - measurementFrequency = 1.0; - zedSetMessageRateByName("NMEA_GSV", measurementFrequency); // One report per second - - zedSetMessageRateByName("NMEA_RMC", 1); - systemPrintln("Reset to Surveying Defaults (NMEAx5)"); - } - else if (incoming == 11) - { - setGNSSMessageRates(settings.ubxMessageRates, 0); // Turn off all messages - zedSetMessageRateByName("NMEA_GGA", 1); - zedSetMessageRateByName("NMEA_GSA", 1); - zedSetMessageRateByName("NMEA_GST", 1); - - // We want GSV NMEA to be reported at 1Hz to avoid swamping SPP connection - float measurementFrequency = (1000.0 / settings.measurementRateMs) / settings.navigationRate; - if (measurementFrequency < 1.0) - measurementFrequency = 1.0; - zedSetMessageRateByName("NMEA_GSV", measurementFrequency); // One report per second - - zedSetMessageRateByName("NMEA_RMC", 1); - - zedSetMessageRateByName("RXM_RAWX", 1); - zedSetMessageRateByName("RXM_SFRBX", 1); - systemPrintln("Reset to PPP Logging Defaults (NMEAx5 + RXMx2)"); - } - else if (incoming == 12) - { - setGNSSMessageRates(settings.ubxMessageRates, 0); // Turn off all messages - systemPrintln("All messages disabled"); - } - else if (incoming == 13) - { - setGNSSMessageRates(settings.ubxMessageRates, 1); // Turn on all messages to report once per fix - systemPrintln("All messages enabled"); - } - else if (incoming == INPUT_RESPONSE_GETNUMBER_EXIT) - break; - else if (incoming == INPUT_RESPONSE_GETNUMBER_TIMEOUT) - break; - else - printUnknown(incoming); - } - - clearBuffer(); // Empty buffer of any newline chars - - // Make sure the appropriate messages are enabled - bool response = gnssSetMessages(MAX_SET_MESSAGES_RETRIES); // Does a complete open/closed val set - if (response == false) - systemPrintf("menuMessages: Failed to enable messages - after %d tries", MAX_SET_MESSAGES_RETRIES); - else - systemPrintln("menuMessages: Messages successfully enabled"); - - setLoggingType(); // Update Standard, PPP, or custom for icon selection -} - -// Set RTCM for base mode to defaults (1005/1074/1084/1094/1124 1Hz & 1230 0.1Hz) -void zedBaseRtcmDefault() -{ - int firstRTCMRecord = zedGetMessageNumberByName("RTCM_1005"); - settings.ubxMessageRatesBase[zedGetMessageNumberByName("RTCM_1005") - firstRTCMRecord] = 1; // 1105 - settings.ubxMessageRatesBase[zedGetMessageNumberByName("RTCM_1074") - firstRTCMRecord] = 1; // 1074 - settings.ubxMessageRatesBase[zedGetMessageNumberByName("RTCM_1077") - firstRTCMRecord] = 0; // 1077 - settings.ubxMessageRatesBase[zedGetMessageNumberByName("RTCM_1084") - firstRTCMRecord] = 1; // 1084 - settings.ubxMessageRatesBase[zedGetMessageNumberByName("RTCM_1087") - firstRTCMRecord] = 0; // 1087 - - settings.ubxMessageRatesBase[zedGetMessageNumberByName("RTCM_1094") - firstRTCMRecord] = 1; // 1094 - settings.ubxMessageRatesBase[zedGetMessageNumberByName("RTCM_1097") - firstRTCMRecord] = 0; // 1097 - settings.ubxMessageRatesBase[zedGetMessageNumberByName("RTCM_1124") - firstRTCMRecord] = 1; // 1124 - settings.ubxMessageRatesBase[zedGetMessageNumberByName("RTCM_1127") - firstRTCMRecord] = 0; // 1127 - settings.ubxMessageRatesBase[zedGetMessageNumberByName("RTCM_1230") - firstRTCMRecord] = 10; // 1230 - - settings.ubxMessageRatesBase[zedGetMessageNumberByName("RTCM_4072_0") - firstRTCMRecord] = 0; // 4072_0 - settings.ubxMessageRatesBase[zedGetMessageNumberByName("RTCM_4072_1") - firstRTCMRecord] = 0; // 4072_1 -} - -// Reset to Low Bandwidth Link (1074/1084/1094/1124 0.5Hz & 1005/1230 0.1Hz) -void zedBaseRtcmLowDataRate() -{ - int firstRTCMRecord = zedGetMessageNumberByName("RTCM_1005"); - settings.ubxMessageRatesBase[zedGetMessageNumberByName("RTCM_1005") - firstRTCMRecord] = 10; // 1105 0.1Hz - settings.ubxMessageRatesBase[zedGetMessageNumberByName("RTCM_1074") - firstRTCMRecord] = 2; // 1074 0.5Hz - settings.ubxMessageRatesBase[zedGetMessageNumberByName("RTCM_1077") - firstRTCMRecord] = 0; // 1077 - settings.ubxMessageRatesBase[zedGetMessageNumberByName("RTCM_1084") - firstRTCMRecord] = 2; // 1084 0.5Hz - settings.ubxMessageRatesBase[zedGetMessageNumberByName("RTCM_1087") - firstRTCMRecord] = 0; // 1087 - - settings.ubxMessageRatesBase[zedGetMessageNumberByName("RTCM_1094") - firstRTCMRecord] = 2; // 1094 0.5Hz - settings.ubxMessageRatesBase[zedGetMessageNumberByName("RTCM_1097") - firstRTCMRecord] = 0; // 1097 - settings.ubxMessageRatesBase[zedGetMessageNumberByName("RTCM_1124") - firstRTCMRecord] = 2; // 1124 0.5Hz - settings.ubxMessageRatesBase[zedGetMessageNumberByName("RTCM_1127") - firstRTCMRecord] = 0; // 1127 - settings.ubxMessageRatesBase[zedGetMessageNumberByName("RTCM_1230") - firstRTCMRecord] = 10; // 1230 0.1Hz - - settings.ubxMessageRatesBase[zedGetMessageNumberByName("RTCM_4072_0") - firstRTCMRecord] = 0; // 4072_0 - settings.ubxMessageRatesBase[zedGetMessageNumberByName("RTCM_4072_1") - firstRTCMRecord] = 0; // 4072_1 -} - -char *zedGetRtcmDefaultString() -{ - return ((char *)"1005/1074/1084/1094/1124 1Hz & 1230 0.1Hz"); -} -char *zedGetRtcmLowDataRateString() -{ - return ((char *)"1074/1084/1094/1124 0.5Hz & 1005/1230 0.1Hz"); -} - -int ubxConstellationIDToIndex(int id) -{ - for (int x = 0; x < MAX_UBX_CONSTELLATIONS; x++) - { - if (settings.ubxConstellations[x].gnssID == id) - return x; - } - - return -1; // Should never be reached...! -} - -// Controls the constellations that are used to generate a fix and logged -void zedMenuConstellations() -{ - while (1) - { - systemPrintln(); - systemPrintln("Menu: Constellations"); - - for (int x = 0; x < MAX_UBX_CONSTELLATIONS; x++) - { - systemPrintf("%d) Constellation %s: ", x + 1, settings.ubxConstellations[x].textName); - if (settings.ubxConstellations[x].enabled == true) - systemPrint("Enabled"); - else - systemPrint("Disabled"); - systemPrintln(); - } - - systemPrintln("x) Exit"); - - int incoming = getUserInputNumber(); // Returns EXIT, TIMEOUT, or long - - if (incoming >= 1 && incoming <= MAX_UBX_CONSTELLATIONS) - { - incoming--; // Align choice to constellation array of 0 to 5 - - settings.ubxConstellations[incoming].enabled ^= 1; - - // 3.10.6: To avoid cross-correlation issues, it is recommended that GPS and QZSS are always both enabled or - // both disabled. - if (incoming == ubxConstellationIDToIndex(SFE_UBLOX_GNSS_ID_GPS)) // Match QZSS to GPS - { - settings.ubxConstellations[ubxConstellationIDToIndex(SFE_UBLOX_GNSS_ID_QZSS)].enabled = - settings.ubxConstellations[incoming].enabled; - } - if (incoming == ubxConstellationIDToIndex(SFE_UBLOX_GNSS_ID_QZSS)) // Match GPS to QZSS - { - settings.ubxConstellations[ubxConstellationIDToIndex(SFE_UBLOX_GNSS_ID_GPS)].enabled = - settings.ubxConstellations[incoming].enabled; - } - } - else if (incoming == INPUT_RESPONSE_GETNUMBER_EXIT) - break; - else if (incoming == INPUT_RESPONSE_GETNUMBER_TIMEOUT) - break; - else - printUnknown(incoming); - } - - // Apply current settings to module - gnssSetConstellations(); - - clearBuffer(); // Empty buffer of any newline chars -} - -// Given a unique string, find first and last records containing that string in message array -void zedSetMessageOffsets(const ubxMsg *localMessage, const char *messageType, int &startOfBlock, int &endOfBlock) -{ - if (present.gnss_zedf9p) - { - char messageNamePiece[40]; // UBX_RTCM - snprintf(messageNamePiece, sizeof(messageNamePiece), "%s", messageType); // Put UBX_ infront of type - - // Find the first occurrence - for (startOfBlock = 0; startOfBlock < MAX_UBX_MSG; startOfBlock++) - { - if (strstr(localMessage[startOfBlock].msgTextName, messageNamePiece) != nullptr) - break; - } - if (startOfBlock == MAX_UBX_MSG) - { - // Error out - startOfBlock = 0; - endOfBlock = 0; - return; - } - - // Find the last occurrence - for (endOfBlock = startOfBlock + 1; endOfBlock < MAX_UBX_MSG; endOfBlock++) - { - if (strstr(localMessage[endOfBlock].msgTextName, messageNamePiece) == nullptr) - break; - } - } - else - systemPrintln("zedSetMessageOffsets() Platform not supported"); -} - -// Count the number of NAV2 messages with rates more than 0. Used for determining if we need the enable -// the global NAV2 feature. -uint8_t zedGetNAV2MessageCount() -{ - if (present.gnss_zedf9p) - { - int enabledMessages = 0; - int startOfBlock = 0; - int endOfBlock = 0; - - zedSetMessageOffsets(&ubxMessages[0], "NAV2", startOfBlock, - endOfBlock); // Find start and stop of given messageType in message array - - for (int x = 0; x < (endOfBlock - startOfBlock); x++) - { - if (settings.ubxMessageRates[x + startOfBlock] > 0) - enabledMessages++; - } - - zedSetMessageOffsets(&ubxMessages[0], "NMEANAV2", startOfBlock, - endOfBlock); // Find start and stop of given messageType in message array - - for (int x = 0; x < (endOfBlock - startOfBlock); x++) - { - if (settings.ubxMessageRates[x + startOfBlock] > 0) - enabledMessages++; - } - - return (enabledMessages); - } - else - systemPrintln("zedGetNAV2MessageCount() Platform not supported"); - - return (false); -} - -// Given the name of a message, find it, and set the rate -bool zedSetMessageRateByName(const char *msgName, uint8_t msgRate) -{ - if (present.gnss_zedf9p) - { - for (int x = 0; x < MAX_UBX_MSG; x++) - { - if (strcmp(ubxMessages[x].msgTextName, msgName) == 0) - { - settings.ubxMessageRates[x] = msgRate; - return (true); - } - } - } - else - systemPrintln("zedSetMessageRateByName() Platform not supported"); - - systemPrintf("zedSetMessageRateByName: %s not found\r\n", msgName); - return (false); -} - -// Given the name of a message, find it, and return the rate -uint8_t zedGetMessageRateByName(const char *msgName) -{ - if (present.gnss_zedf9p) - { - return (settings.ubxMessageRates[zedGetMessageNumberByName(msgName)]); - } - else - systemPrintln("zedGetMessageRateByName() Platform not supported"); - - return (0); -} - -// Given the name of a message, return the array number -uint8_t zedGetMessageNumberByName(const char *msgName) -{ - if (present.gnss_zedf9p) - { - for (int x = 0; x < MAX_UBX_MSG; x++) - { - if (strcmp(ubxMessages[x].msgTextName, msgName) == 0) - return (x); - } - } - else - systemPrintln("zedGetMessageNumberByName() Platform not supported"); - - systemPrintf("zedGetMessageNumberByName: %s not found\r\n", msgName); - return (0); -} - -uint32_t zedGetRadioBaudRate() -{ - if (present.gnss_zedf9p) - { - return theGNSS->getVal32(UBLOX_CFG_UART2_BAUDRATE); - } - - return (0); -} - -bool zedSetRadioBaudRate(uint32_t baud) -{ - if (present.gnss_zedf9p) - { - return theGNSS->setVal32(UBLOX_CFG_UART2_BAUDRATE, baud); - } - - return false; -} - -uint32_t zedGetDataBaudRate() -{ - if (present.gnss_zedf9p) - { - return theGNSS->getVal32(UBLOX_CFG_UART1_BAUDRATE); - } - - return (0); -} - -bool zedSetDataBaudRate(uint32_t baud) -{ - if (present.gnss_zedf9p) - { - return theGNSS->setVal32(UBLOX_CFG_UART1_BAUDRATE, baud); - } - - return false; -} - -bool zedCheckGnssNMEARates() -{ - if (present.gnss_zedf9p) - { - return (zedGetMessageRateByName("NMEA_GGA") > 0 && zedGetMessageRateByName("NMEA_GSA") > 0 && - zedGetMessageRateByName("NMEA_GST") > 0 && zedGetMessageRateByName("NMEA_GSV") > 0 && - zedGetMessageRateByName("NMEA_RMC") > 0); - } - - return false; -} - -bool zedCheckGnssPPPRates() -{ - if (present.gnss_zedf9p) - { - return (zedGetMessageRateByName("RXM_RAWX") > 0 && zedGetMessageRateByName("RXM_SFRBX") > 0); - } - - return false; -} diff --git a/Firmware/RTK_Everywhere/menuBase.ino b/Firmware/RTK_Everywhere/menuBase.ino index 13f186b30..31391828b 100644 --- a/Firmware/RTK_Everywhere/menuBase.ino +++ b/Firmware/RTK_Everywhere/menuBase.ino @@ -100,7 +100,7 @@ void menuBase() } systemPrintf("4) Set required initial positional accuracy before Survey-In: %0.2f meters\r\n", - gnssGetSurveyInStartingAccuracy()); + gnss->getSurveyInStartingAccuracy()); } } diff --git a/Firmware/RTK_Everywhere/menuCommands.ino b/Firmware/RTK_Everywhere/menuCommands.ino index e9bfad0f7..c38700309 100644 --- a/Firmware/RTK_Everywhere/menuCommands.ino +++ b/Firmware/RTK_Everywhere/menuCommands.ino @@ -713,7 +713,8 @@ SettingValueResponse updateSettingWithValue(bool inCommands, const char *setting } break; case tUbMsgRtb: { - int firstRTCMRecord = zedGetMessageNumberByName("RTCM_1005"); + GNSS_ZED * zed = (GNSS_ZED *)gnss; + int firstRTCMRecord = zed->getMessageNumberByName("RTCM_1005"); for (int x = 0; x < qualifier; x++) { @@ -1014,7 +1015,7 @@ SettingValueResponse updateSettingWithValue(bool inCommands, const char *setting else if (strcmp(settingName, "measurementRateHz") == 0) { - gnssSetRate(1.0 / settingValue); + gnss->setRate(1.0 / settingValue); // This is one of the first settings to be received. If seen, remove the station files. removeFile(stationCoordinateECEFFileName); @@ -1047,7 +1048,7 @@ SettingValueResponse updateSettingWithValue(bool inCommands, const char *setting else if (strcmp(settingName, "minCNO") == 0) { // Note: this sends the Min CNO to the GNSS, as well as saving it in settings... Is this what we want? TODO - gnssSetMinCno(settingValue); + gnss->setMinCno(settingValue); knownSetting = true; } else if (strcmp(settingName, "fixedHAEAPC") == 0) @@ -1468,7 +1469,8 @@ void createSettingsString(char *newSettings) case tUbMsgRtb: { // Record message settings - int firstRTCMRecord = zedGetMessageNumberByName("RTCM_1005"); + GNSS_ZED * zed = (GNSS_ZED *)gnss; + int firstRTCMRecord = zed->getMessageNumberByName("RTCM_1005"); for (int x = 0; x < rtkSettingsEntries[i].qualifier; x++) { @@ -1721,7 +1723,7 @@ void createSettingsString(char *newSettings) stringRecord(newSettings, "fixedBaseCoordinateTypeGeo", true); } - stringRecord(newSettings, "measurementRateHz", 1.0 / gnssGetRateS(), 2); // 2 = decimals to print + stringRecord(newSettings, "measurementRateHz", 1.0 / gnss->getRateS(), 2); // 2 = decimals to print // System state at power on. Convert various system states to either Rover or Base or NTP. int lastState; // 0 = Rover, 1 = Base, 2 = NTP @@ -1759,7 +1761,7 @@ void createSettingsString(char *newSettings) stringRecord(newSettings, "wifiConfigOverAP", 0); // 1 = AP mode, 0 = WiFi // Single variables needed on Config page - stringRecord(newSettings, "minCNO", gnssGetMinCno()); + stringRecord(newSettings, "minCNO", gnss->getMinCno()); stringRecord(newSettings, "enableRCFirmware", enableRCFirmware); // Add SD Characteristics @@ -1795,15 +1797,15 @@ void createSettingsString(char *newSettings) stringRecord(newSettings, "daysRemaining", apDaysRemaining); // Current coordinates come from HPPOSLLH call back - stringRecord(newSettings, "geodeticLat", gnssGetLatitude(), haeNumberOfDecimals); - stringRecord(newSettings, "geodeticLon", gnssGetLongitude(), haeNumberOfDecimals); - stringRecord(newSettings, "geodeticAlt", gnssGetAltitude(), 3); + stringRecord(newSettings, "geodeticLat", gnss->getLatitude(), haeNumberOfDecimals); + stringRecord(newSettings, "geodeticLon", gnss->getLongitude(), haeNumberOfDecimals); + stringRecord(newSettings, "geodeticAlt", gnss->getAltitude(), 3); double ecefX = 0; double ecefY = 0; double ecefZ = 0; - geodeticToEcef(gnssGetLatitude(), gnssGetLongitude(), gnssGetAltitude(), &ecefX, &ecefY, &ecefZ); + geodeticToEcef(gnss->getLatitude(), gnss->getLongitude(), gnss->getAltitude(), &ecefX, &ecefY, &ecefZ); stringRecord(newSettings, "ecefX", ecefX, 3); stringRecord(newSettings, "ecefY", ecefY, 3); @@ -2243,7 +2245,8 @@ SettingValueResponse getSettingValue(bool inCommands, const char *settingName, c } break; case tUbMsgRtb: { - int firstRTCMRecord = zedGetMessageNumberByName("RTCM_1005"); + GNSS_ZED * zed = (GNSS_ZED *)gnss; + int firstRTCMRecord = zed->getMessageNumberByName("RTCM_1005"); for (int x = 0; x < qualifier; x++) { @@ -2739,7 +2742,8 @@ void commandList(bool inCommands, int i) break; case tUbMsgRtb: { // Record message settings - int firstRTCMRecord = zedGetMessageNumberByName("RTCM_1005"); + GNSS_ZED * zed = (GNSS_ZED *)gnss; + int firstRTCMRecord = zed->getMessageNumberByName("RTCM_1005"); for (int x = 0; x < rtkSettingsEntries[i].qualifier; x++) { diff --git a/Firmware/RTK_Everywhere/menuFirmware.ino b/Firmware/RTK_Everywhere/menuFirmware.ino index e8aeecbc4..2264465ca 100644 --- a/Firmware/RTK_Everywhere/menuFirmware.ino +++ b/Firmware/RTK_Everywhere/menuFirmware.ino @@ -398,7 +398,7 @@ void updateFromSD(const char *firmwareFileName) firmwareFile.close(); sd->remove(firmwareFileName); - gnssFactoryReset(); + gnss->factoryReset(); } delay(1000); diff --git a/Firmware/RTK_Everywhere/menuGNSS.ino b/Firmware/RTK_Everywhere/menuGNSS.ino index 64ab49511..a1523854f 100644 --- a/Firmware/RTK_Everywhere/menuGNSS.ino +++ b/Firmware/RTK_Everywhere/menuGNSS.ino @@ -14,10 +14,10 @@ void menuGNSS() if (!present.gnss_mosaicX5) { systemPrint("1) Set measurement rate in Hz: "); - systemPrintln(1.0 / gnssGetRateS(), 5); + systemPrintln(1.0 / gnss->getRateS(), 5); systemPrint("2) Set measurement rate in seconds between measurements: "); - systemPrintln(gnssGetRateS(), 5); + systemPrintln(gnss->getRateS(), 5); systemPrintln(" Note: The measurement rate is overridden to 1Hz when in Base mode."); } @@ -115,7 +115,7 @@ void menuGNSS() systemPrintf("5) Minimum elevation for a GNSS satellite to be used in fix (degrees): %d\r\n", settings.minElev); - systemPrintf("6) Minimum satellite signal level for navigation (dBHz): %d\r\n", gnssGetMinCno()); + systemPrintf("6) Minimum satellite signal level for navigation (dBHz): %d\r\n", gnss->getMinCno()); systemPrint("7) Toggle NTRIP Client: "); if (settings.enableNtripClient == true) @@ -166,8 +166,8 @@ void menuGNSS() if (getNewSetting("Enter GNSS measurement rate in Hz", 0.00012, 20.0, &rate) == INPUT_RESPONSE_VALID) // 20Hz limit with all constellations enabled { - gnssSetRate(1.0 / rate); // Convert Hz to seconds. This will set settings.measurementRateMs, - // settings.navigationRate, and GSV message + gnss->setRate(1.0 / rate); // Convert Hz to seconds. This will set settings.measurementRateMs, + // settings.navigationRate, and GSV message } } else if ((incoming == 2) && (!present.gnss_mosaicX5)) @@ -190,7 +190,7 @@ void menuGNSS() if (getNewSetting("Enter GNSS measurement rate in seconds between measurements", minRate, maxRate, &rate) == INPUT_RESPONSE_VALID) { - gnssSetRate(rate); // This will set settings.measurementRateMs, settings.navigationRate, and GSV message + gnss->setRate(rate); // This will set settings.measurementRateMs, settings.navigationRate, and GSV message } } else if (incoming == 3) @@ -238,7 +238,7 @@ void menuGNSS() else settings.dynamicModel = dynamicModel; // Recorded to NVM and file at main menu exit - gnssSetModel(settings.dynamicModel); + gnss->setModel(settings.dynamicModel); } } else if (present.gnss_um980) @@ -250,7 +250,7 @@ void menuGNSS() dynamicModel -= 1; // Align to 0 to 2 settings.dynamicModel = dynamicModel; // Recorded to NVM and file at main menu exit - gnssSetModel(settings.dynamicModel); + gnss->setModel(settings.dynamicModel); } } else if (present.gnss_mosaicX5) @@ -262,14 +262,14 @@ void menuGNSS() dynamicModel -= 1; // Align to 0 to MAX_MOSAIC_RX_DYNAMICS - 1 settings.dynamicModel = dynamicModel; // Recorded to NVM and file at main menu exit - gnssSetModel(settings.dynamicModel); + gnss->setModel(settings.dynamicModel); } } } } else if (incoming == 4) { - gnssMenuConstellations(); + gnss->menuConstellations(); } else if (incoming == 5) @@ -277,7 +277,7 @@ void menuGNSS() // Arbitrary 90 degree max if (getNewSetting("Enter minimum elevation in degrees", 0, 90, &settings.minElev) == INPUT_RESPONSE_VALID) { - gnssSetElevation(settings.minElev); + gnss->setElevation(settings.minElev); } } else if (incoming == 6) @@ -288,7 +288,7 @@ void menuGNSS() present.gnss_mosaicX5 ? 60 : 90, &minCNO) == INPUT_RESPONSE_VALID) { - gnssSetMinCno(minCNO); // Set the setting and configure the GNSS receiver + gnss->setMinCno(minCNO); // Set the setting and configure the GNSS receiver } } diff --git a/Firmware/RTK_Everywhere/menuMain.ino b/Firmware/RTK_Everywhere/menuMain.ino index ce13d5f89..4fd2488db 100644 --- a/Firmware/RTK_Everywhere/menuMain.ino +++ b/Firmware/RTK_Everywhere/menuMain.ino @@ -39,7 +39,7 @@ void terminalUpdate() // Push RTCM to GNSS module over I2C / SPI if (correctionLastSeen(CORR_USB)) - gnssPushRawData((uint8_t *)buffer, length); + gnss->pushRawData((uint8_t *)buffer, length); } // Does incoming data consist of RTCM correction messages @@ -58,7 +58,7 @@ void terminalUpdate() // Push RTCM to GNSS module over I2C / SPI if (correctionLastSeen(CORR_USB)) - gnssPushRawData((uint8_t *)buffer, length); + gnss->pushRawData((uint8_t *)buffer, length); } else { @@ -117,7 +117,7 @@ void menuMain() if (settings.debugGnss == true) { // Turn off GNSS debug while in config menus - gnssDisableDebugging(); + gnss->debuggingDisable(); } // Check for remote app config entry into command mode @@ -255,7 +255,7 @@ void menuMain() if (incoming == 1) menuGNSS(); else if (incoming == 2) - gnssMenuMessages(); + gnss->menuMessages(); else if (incoming == 3) menuBase(); else if (incoming == 4) @@ -322,7 +322,7 @@ void menuMain() requestChangeState(STATE_ROVER_NOT_STARTED); // Restart rover upon exit for latest changes to take effect } - gnssSaveConfiguration(); + gnss->saveConfiguration(); recordSystemSettings(); // Once all menus have exited, record the new settings to LittleFS and config file } @@ -330,7 +330,7 @@ void menuMain() if (settings.debugGnss == true) { // Re-enable GNSS debug once we exit config menus - gnssEnableDebugging(); + gnss->debuggingEnable(); } clearBuffer(); // Empty buffer of any newline chars @@ -549,7 +549,7 @@ void factoryReset(bool alreadyHasSemaphore) LittleFS.format(); if (online.gnss == true) - gnssFactoryReset(); + gnss->factoryReset(); systemPrintln("Settings erased successfully. Rebooting. Goodbye!"); delay(2000); diff --git a/Firmware/RTK_Everywhere/menuMessages.ino b/Firmware/RTK_Everywhere/menuMessages.ino index 649a39bc7..b70872795 100644 --- a/Firmware/RTK_Everywhere/menuMessages.ino +++ b/Firmware/RTK_Everywhere/menuMessages.ino @@ -153,8 +153,8 @@ void menuMessagesBaseRTCM() systemPrintln("1) Set RXM Messages for Base Mode"); - systemPrintf("2) Reset to Defaults (%s)\r\n", gnssGetRtcmDefaultString()); - systemPrintf("3) Reset to Low Bandwidth Link (%s)\r\n", gnssGetRtcmLowDataRateString()); + systemPrintf("2) Reset to Defaults (%s)\r\n", gnss->getRtcmDefaultString()); + systemPrintf("3) Reset to Low Bandwidth Link (%s)\r\n", gnss->getRtcmLowDataRateString()); systemPrintln("x) Exit"); @@ -162,21 +162,21 @@ void menuMessagesBaseRTCM() if (incoming == 1) { - gnssMenuMessageBaseRtcm(); + gnss->menuMessageBaseRtcm(); restartBase = true; } else if (incoming == 2) { - gnssBaseRtcmDefault(); + gnss->baseRtcmDefault(); - systemPrintf("Reset to Defaults (%s)\r\n", gnssGetRtcmDefaultString()); + systemPrintf("Reset to Defaults (%s)\r\n", gnss->getRtcmDefaultString()); restartBase = true; } else if (incoming == 3) { - gnssBaseRtcmLowDataRate(); + gnss->baseRtcmLowDataRate(); - systemPrintf("Reset to Low Bandwidth Link (%s)\r\n", gnssGetRtcmLowDataRateString()); + systemPrintf("Reset to Low Bandwidth Link (%s)\r\n", gnss->getRtcmLowDataRateString()); restartBase = true; } else if (incoming == INPUT_RESPONSE_GETNUMBER_EXIT) @@ -203,15 +203,16 @@ void zedMenuMessagesSubtype(uint8_t *localMessageRate, const char *messageType) int endOfBlock = 0; int rtcmOffset = 0; // Used to offset messageSupported lookup + GNSS_ZED * zed = (GNSS_ZED *)gnss; if (strcmp(messageType, "RTCM-Base") == 0) // The ubxMessageRatesBase array is 0 to MAX_UBX_MSG_RTCM - 1 { startOfBlock = 0; endOfBlock = MAX_UBX_MSG_RTCM; - rtcmOffset = zedGetMessageNumberByName("RTCM_1005"); + rtcmOffset = zed->getMessageNumberByName("RTCM_1005"); } else - zedSetMessageOffsets(&ubxMessages[0], messageType, startOfBlock, - endOfBlock); // Find start and stop of given messageType in message array + zed->setMessageOffsets(&ubxMessages[0], messageType, startOfBlock, + endOfBlock); // Find start and stop of given messageType in message array for (int x = 0; x < (endOfBlock - startOfBlock); x++) { @@ -624,7 +625,8 @@ void checkGNSSArrayDefaults() defaultsApplied = true; // Reset Base rates to defaults - int firstRTCMRecord = zedGetMessageNumberByName("RTCM_1005"); + GNSS_ZED * zed = (GNSS_ZED *)gnss; + int firstRTCMRecord = zed->getMessageNumberByName("RTCM_1005"); for (int x = 0; x < MAX_UBX_MSG_RTCM; x++) settings.ubxMessageRatesBase[x] = ubxMessages[firstRTCMRecord + x].msgDefaultRate; } @@ -756,14 +758,14 @@ void setLoggingType() { loggingType = LOGGING_CUSTOM; - int messageCount = gnssGetActiveMessageCount(); + int messageCount = gnss->getActiveMessageCount(); if (messageCount == 5 || messageCount == 7) { - if (checkGnssNMEARates()) + if (gnss->checkNMEARates()) { loggingType = LOGGING_STANDARD; - if (checkGnssPPPRates()) + if (gnss->checkPPPRates()) loggingType = LOGGING_PPP; } } @@ -773,30 +775,31 @@ void setLoggingType() void setLogTestFrequencyMessages(int rate, int messages) { // Set measurement frequency - gnssSetRate(1.0 / (double)rate); // Convert Hz to seconds. This will set settings.measurementRateMs, + gnss->setRate(1.0 / (double)rate); // Convert Hz to seconds. This will set settings.measurementRateMs, // settings.navigationRate, and GSV message // Set messages setGNSSMessageRates(settings.ubxMessageRates, 0); // Turn off all messages + GNSS_ZED * zed = (GNSS_ZED *)gnss; if (messages == 5) { - zedSetMessageRateByName("NMEA_GGA", 1); - zedSetMessageRateByName("NMEA_GSA", 1); - zedSetMessageRateByName("NMEA_GST", 1); - zedSetMessageRateByName("NMEA_GSV", rate); // One report per second - zedSetMessageRateByName("NMEA_RMC", 1); + zed->setMessageRateByName("NMEA_GGA", 1); + zed->setMessageRateByName("NMEA_GSA", 1); + zed->setMessageRateByName("NMEA_GST", 1); + zed->setMessageRateByName("NMEA_GSV", rate); // One report per second + zed->setMessageRateByName("NMEA_RMC", 1); log_d("Messages: Surveying Defaults (NMEAx5)"); } else if (messages == 7) { - zedSetMessageRateByName("NMEA_GGA", 1); - zedSetMessageRateByName("NMEA_GSA", 1); - zedSetMessageRateByName("NMEA_GST", 1); - zedSetMessageRateByName("NMEA_GSV", rate); // One report per second - zedSetMessageRateByName("NMEA_RMC", 1); - zedSetMessageRateByName("RXM_RAWX", 1); - zedSetMessageRateByName("RXM_SFRBX", 1); + zed->setMessageRateByName("NMEA_GGA", 1); + zed->setMessageRateByName("NMEA_GSA", 1); + zed->setMessageRateByName("NMEA_GST", 1); + zed->setMessageRateByName("NMEA_GSV", rate); // One report per second + zed->setMessageRateByName("NMEA_RMC", 1); + zed->setMessageRateByName("RXM_RAWX", 1); + zed->setMessageRateByName("RXM_SFRBX", 1); log_d("Messages: PPP NMEAx5+RXMx2"); } @@ -804,8 +807,8 @@ void setLogTestFrequencyMessages(int rate, int messages) log_d("Unknown message amount"); // Apply these message rates to both UART1 / SPI and USB - gnssSetMessages(MAX_SET_MESSAGES_RETRIES); // Does a complete open/closed val set - gnssSetMessagesUsb(MAX_SET_MESSAGES_RETRIES); + gnss->setMessages(MAX_SET_MESSAGES_RETRIES); // Does a complete open/closed val set + gnss->setMessagesUsb(MAX_SET_MESSAGES_RETRIES); } // The log test allows us to record a series of different system configurations into diff --git a/Firmware/RTK_Everywhere/menuPP.ino b/Firmware/RTK_Everywhere/menuPP.ino index cd803fec7..ea6babeb2 100644 --- a/Firmware/RTK_Everywhere/menuPP.ino +++ b/Firmware/RTK_Everywhere/menuPP.ino @@ -581,7 +581,7 @@ long long thingstreamEpochToGPSEpoch(long long startEpoch) epoch /= 1000; // Convert PointPerfect ms Epoch to s // Convert Unix Epoch time from PointPerfect to GPS Time Of Week needed for UBX message - long long gpsEpoch = epoch - 315964800 + gnssGetLeapSeconds(); // Shift to GPS Epoch. + long long gpsEpoch = epoch - 315964800 + gnss->getLeapSeconds(); // Shift to GPS Epoch. return (gpsEpoch); } @@ -652,7 +652,7 @@ void dateToKeyStart(uint8_t expDay, uint8_t expMonth, uint16_t expYear, uint64_t long long startUnixEpoch = expireUnixEpoch - (27 * 24 * 60 * 60); // Move back 27 days // Additionally, Thingstream seems to be reporting Epochs that do not have leap seconds - startUnixEpoch -= gnssGetLeapSeconds(); // Modify our Epoch to match Point Perfect + startUnixEpoch -= gnss->getLeapSeconds(); // Modify our Epoch to match Point Perfect // PointPerfect uses/reports unix epochs in milliseconds *settingsKeyStart = startUnixEpoch * 1000L; // Convert to ms @@ -727,13 +727,14 @@ void pushRXMPMP(UBX_RXM_PMP_message_data_t *pmpData) if (correctionLastSeen(CORR_LBAND)) { - updateZEDCorrectionsSource(1); // Set SOURCE to 1 (L-Band) if needed + GNSS_ZED * zed = (GNSS_ZED *)gnss; + zed->updateCorrectionsSource(1); // Set SOURCE to 1 (L-Band) if needed if (settings.debugCorrections == true && !inMainMenu) systemPrintf("Pushing %d bytes of RXM-PMP data to GNSS\r\n", payloadLen); - gnssPushRawData(&pmpData->sync1, (size_t)payloadLen + 6); // Push the sync chars, class, ID, length and payload - gnssPushRawData(&pmpData->checksumA, (size_t)2); // Push the checksum bytes + gnss->pushRawData(&pmpData->sync1, (size_t)payloadLen + 6); // Push the sync chars, class, ID, length and payload + gnss->pushRawData(&pmpData->checksumA, (size_t)2); // Push the checksum bytes } else { @@ -800,12 +801,12 @@ void beginLBand() printNEOInfo(); // Print module firmware version } - gnssUpdate(); + gnss->update(); uint32_t LBandFreq; - uint8_t fixType = gnssGetFixType(); - double latitude = gnssGetLatitude(); - double longitude = gnssGetLongitude(); + uint8_t fixType = gnss->getFixType(); + double latitude = gnss->getLatitude(); + double longitude = gnss->getLongitude(); // If we have a fix, check which frequency to use if (fixType >= 2 && fixType <= 5) // 2D, 3D, 3D+DR, or Time { @@ -854,7 +855,8 @@ void beginLBand() response &= i2cLBand.sendCfgValset(); - response &= zedEnableLBandCommunication(); + GNSS_ZED * zed = (GNSS_ZED *)gnss; + response &= zed->lBandCommunicationEnable(); if (response == false) systemPrintln("L-Band failed to configure"); @@ -864,7 +866,7 @@ void beginLBand() if (settings.debugCorrections == true) systemPrintln("L-Band online"); - gnssApplyPointPerfectKeys(); // Apply keys now, if we have them. This sets online.lbandCorrections + gnss->applyPointPerfectKeys(); // Apply keys now, if we have them. This sets online.lbandCorrections online.lband_neo = true; } @@ -873,9 +875,9 @@ void beginLBand() if (present.gnss_mosaicX5 && settings.enablePointPerfectCorrections) { uint32_t LBandFreq; - uint8_t fixType = gnssGetFixType(); - double latitude = gnssGetLatitude(); - double longitude = gnssGetLongitude(); + uint8_t fixType = gnss->getFixType(); + double latitude = gnss->getLatitude(); + double longitude = gnss->getLongitude(); // If we have a fix, check which frequency to use if (fixType >= 1) // Stand-Alone PVT or better { @@ -1122,7 +1124,7 @@ void menuPointPerfect() if (strlen(settings.pointPerfectClientID) > 0) { - gnssApplyPointPerfectKeys(); + gnss->applyPointPerfectKeys(); } clearBuffer(); // Empty buffer of any newline chars @@ -1158,7 +1160,7 @@ void updateLBand() lbandCorrectionsReceived = false; // If we don't get an L-Band fix within Timeout, hot-start ZED-F9x - if (gnssIsRTKFloat()) + if (gnss->isRTKFloat()) { if (lbandTimeFloatStarted == 0) lbandTimeFloatStarted = millis(); @@ -1182,15 +1184,15 @@ void updateLBand() lbandTimeFloatStarted = millis(); // Restart timer for L-Band. Don't immediately reset ZED to achieve fix. - // Hotstart ZED to try to get RTK lock - theGNSS->softwareResetGNSSOnly(); + // Hotstart GNSS to try to get RTK lock + gnss->softwareReset(); if (settings.debugCorrections == true) systemPrintf("Restarting ZED. Number of Float lock restarts: %d\r\n", floatLockRestarts); } } } - else if (gnssIsRTKFix() && rtkTimeToFixMs == 0) + else if (gnss->isRTKFix() && rtkTimeToFixMs == 0) { lbandTimeFloatStarted = 0; // Restart timer in case we drop from RTK Fix @@ -1494,9 +1496,9 @@ void updateProvisioning() paintLBandConfigure(); // Be sure we ignore any external RTCM sources - gnssDisableRtcmOnGnss(); + gnss->rtcmOnGnssDisable(); - gnssApplyPointPerfectKeys(); // Send current keys, if available, to GNSS + gnss->applyPointPerfectKeys(); // Send current keys, if available, to GNSS settings.requestKeyUpdate = false; // However we got here, clear requestKeyUpdate recordSystemSettings(); // Record these settings to unit diff --git a/Firmware/RTK_Everywhere/menuPorts.ino b/Firmware/RTK_Everywhere/menuPorts.ino index 59cf46671..79377c0c5 100644 --- a/Firmware/RTK_Everywhere/menuPorts.ino +++ b/Firmware/RTK_Everywhere/menuPorts.ino @@ -57,11 +57,11 @@ void menuPortsNoMux() systemPrintln("Menu: Ports"); systemPrint("1) Set serial baud rate for Radio Port: "); - systemPrint(gnssGetRadioBaudRate()); + systemPrint(gnss->getRadioBaudRate()); systemPrintln(" bps"); systemPrint("2) Set serial baud rate for Data Port: "); - systemPrint(gnssGetDataBaudRate()); + systemPrint(gnss->getDataBaudRate()); systemPrintln(" bps"); systemPrint("3) GNSS UART2 UBX Protocol In: "); @@ -87,7 +87,7 @@ void menuPortsNoMux() { settings.radioPortBaud = newBaud; if (online.gnss == true) - gnssSetRadioBaudRate(newBaud); + gnss->setRadioBaudRate(newBaud); } else { @@ -106,7 +106,7 @@ void menuPortsNoMux() { settings.dataPortBaud = newBaud; if (online.gnss == true) - gnssSetDataBaudRate(newBaud); + gnss->setDataBaudRate(newBaud); } else { @@ -147,7 +147,7 @@ void menuPortsMultiplexed() systemPrintln("Menu: Ports"); systemPrint("1) Set Radio port serial baud rate: "); - systemPrint(gnssGetRadioBaudRate()); + systemPrint(gnss->getRadioBaudRate()); systemPrintln(" bps"); systemPrint("2) Set Data port connections: "); @@ -163,7 +163,7 @@ void menuPortsMultiplexed() if (settings.dataPortChannel == MUX_GNSS_UART) { systemPrint("3) Set Data port serial baud rate: "); - systemPrint(gnssGetDataBaudRate()); + systemPrint(gnss->getDataBaudRate()); systemPrintln(" bps"); } else if (settings.dataPortChannel == MUX_PPS_EVENTTRIGGER) @@ -199,7 +199,7 @@ void menuPortsMultiplexed() { settings.radioPortBaud = newBaud; if (online.gnss == true) - gnssSetRadioBaudRate(newBaud); + gnss->setRadioBaudRate(newBaud); } else { @@ -237,7 +237,7 @@ void menuPortsMultiplexed() { settings.dataPortBaud = newBaud; if (online.gnss == true) - gnssSetDataBaudRate(newBaud); + gnss->setDataBaudRate(newBaud); } else { @@ -430,8 +430,8 @@ void menuPortHardwareTriggers() if (updateSettings) { settings.updateGNSSSettings = true; // Force update - gnssBeginExternalEvent(); // Update with new settings - gnssBeginPPS(); + gnss->beginExternalEvent(); // Update with new settings + gnss->beginPPS(); } } diff --git a/Firmware/RTK_Everywhere/menuSystem.ino b/Firmware/RTK_Everywhere/menuSystem.ino index 2227ffe6b..d09b76615 100644 --- a/Firmware/RTK_Everywhere/menuSystem.ino +++ b/Firmware/RTK_Everywhere/menuSystem.ino @@ -15,9 +15,9 @@ void menuSystem() { systemPrint("Online - "); - gnssPrintModuleInfo(); + gnss->printModuleInfo(); - systemPrintf("Module ID: %s\r\n", gnssGetId()); + systemPrintf("Module ID: %s\r\n", gnss->getId()); printCurrentConditions(); } @@ -493,9 +493,9 @@ void menuDebugHardware() settings.debugGnss ^= 1; if (settings.debugGnss) - gnssEnableDebugging(); + gnss->debuggingEnable(); else - gnssDisableDebugging(); + gnss->debuggingDisable(); } else if (incoming == 10) { @@ -1037,7 +1037,7 @@ void menuOperation() } else if (incoming == 10 && present.gnss_zedf9p) { - bool response = gnssSetMessagesUsb(MAX_SET_MESSAGES_RETRIES); + bool response = gnss->setMessagesUsb(MAX_SET_MESSAGES_RETRIES); if (response == false) systemPrintln(F("Failed to enable USB messages")); @@ -1427,19 +1427,19 @@ void printCurrentConditions() if (online.gnss == true) { systemPrint("SIV: "); - systemPrint(gnssGetSatellitesInView()); + systemPrint(gnss->getSatellitesInView()); - float hpa = gnssGetHorizontalAccuracy(); + float hpa = gnss->getHorizontalAccuracy(); char temp[20]; const char *units = getHpaUnits(hpa, temp, sizeof(temp), 3, true); systemPrintf(", HPA (%s): %s", units, temp); systemPrint(", Lat: "); - systemPrint(gnssGetLatitude(), haeNumberOfDecimals); + systemPrint(gnss->getLatitude(), haeNumberOfDecimals); systemPrint(", Lon: "); - systemPrint(gnssGetLongitude(), haeNumberOfDecimals); + systemPrint(gnss->getLongitude(), haeNumberOfDecimals); systemPrint(", Altitude (m): "); - systemPrint(gnssGetAltitude(), 1); + systemPrint(gnss->getAltitude(), 1); systemPrintln(); } @@ -1451,11 +1451,11 @@ void printCurrentConditionsNMEA() { char systemStatus[100]; snprintf(systemStatus, sizeof(systemStatus), - "%02d%02d%02d.%02d,%02d%02d%02d,%0.3f,%d,%0.9f,%0.9f,%0.2f,%d,%d,%d", gnssGetHour(), gnssGetMinute(), - gnssGetSecond(), gnssGetMillisecond(), gnssGetDay(), gnssGetMonth(), - gnssGetYear() % 2000, // Limit to 2 digits - gnssGetHorizontalAccuracy(), gnssGetSatellitesInView(), gnssGetLatitude(), gnssGetLongitude(), - gnssGetAltitude(), gnssGetFixType(), gnssGetCarrierSolution(), batteryLevelPercent); + "%02d%02d%02d.%02d,%02d%02d%02d,%0.3f,%d,%0.9f,%0.9f,%0.2f,%d,%d,%d", gnss->getHour(), gnss->getMinute(), + gnss->getSecond(), gnss->getMillisecond(), gnss->getDay(), gnss->getMonth(), + gnss->getYear() % 2000, // Limit to 2 digits + gnss->getHorizontalAccuracy(), gnss->getSatellitesInView(), gnss->getLatitude(), gnss->getLongitude(), + gnss->getAltitude(), gnss->getFixType(), gnss->getCarrierSolution(), batteryLevelPercent); char nmeaMessage[100]; // Max NMEA sentence length is 82 createNMEASentence(CUSTOM_NMEA_TYPE_STATUS, nmeaMessage, sizeof(nmeaMessage), diff --git a/Firmware/RTK_Everywhere/mosaic.ino b/Firmware/RTK_Everywhere/mosaic.ino index 663bee97d..9c2a1ac65 100644 --- a/Firmware/RTK_Everywhere/mosaic.ino +++ b/Firmware/RTK_Everywhere/mosaic.ino @@ -442,8 +442,8 @@ bool mosaicX5Begin() } // Set COM2 (Radio) and COM3 (Data) baud rates - gnssSetRadioBaudRate(settings.radioPortBaud); - gnssSetDataBaudRate(settings.dataPortBaud); + gnss->setRadioBaudRate(settings.radioPortBaud); + gnss->setDataBaudRate(settings.dataPortBaud); mosaicX5UpdateSD(); // Check card size and free space @@ -743,7 +743,7 @@ bool mosaicX5AutoBaseStart() mosaicDeterminingFixedPosition = true; // Ensure flag is set initially - autoBaseStartTimer = millis(); // Stamp when averaging began + gnss->_autoBaseStartTimer = millis(); // Stamp when averaging began if (response == false) { @@ -1403,7 +1403,7 @@ void mosaicX5MenuMessages() systemPrintln(); systemPrintln("Menu: GNSS Messages"); - systemPrintf("Active messages: %d\r\n", gnssGetActiveMessageCount()); + systemPrintf("Active messages: %d\r\n", gnss->getActiveMessageCount()); systemPrintln("1) Set NMEA Messages"); systemPrintln("2) Set Rover RTCM Messages"); @@ -1733,7 +1733,7 @@ void mosaicX5MenuConstellations() } // Apply current settings to module - gnssSetConstellations(); + gnss->setConstellations(); clearBuffer(); // Empty buffer of any newline chars } diff --git a/Firmware/RTK_Everywhere/settings.h b/Firmware/RTK_Everywhere/settings.h index 8002f4cda..bc0a2c6a4 100644 --- a/Firmware/RTK_Everywhere/settings.h +++ b/Firmware/RTK_Everywhere/settings.h @@ -1,7 +1,8 @@ #ifndef __SETTINGS_H__ #define __SETTINGS_H__ -#include "UM980.h" //Structs of UM980 messages, needed for settings.h +#include "GNSS.h" +#include "GNSS_UM980.h" //Structs of UM980 messages, needed for settings.h #include "mosaic.h" //Structs of mosaic messages, needed for settings.h #include diff --git a/Firmware/RTK_Everywhere/support.ino b/Firmware/RTK_Everywhere/support.ino index 3c7e82a85..12a06cb07 100644 --- a/Firmware/RTK_Everywhere/support.ino +++ b/Firmware/RTK_Everywhere/support.ino @@ -275,7 +275,7 @@ InputResponse getUserInputString(char *userString, uint16_t stringSize, bool loc //=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= // Keep doing these important things while waiting for the user to enter data - gnssUpdate(); // Regularly poll to get latest data + gnss->update(); // Regularly poll to get latest data // Keep processing NTP requests if (online.ethernetNTPServer)