diff --git a/.github/workflows/compile-rtk-everywhere.yml b/.github/workflows/compile-rtk-everywhere.yml index a2c7d8705..5acf26d89 100644 --- a/.github/workflows/compile-rtk-everywhere.yml +++ b/.github/workflows/compile-rtk-everywhere.yml @@ -89,12 +89,12 @@ jobs: "SparkFun u-blox GNSS v3"@3.1.8 "SparkFun Qwiic OLED Arduino Library"@1.0.13 SSLClientESP32@2.0.0 - "SparkFun Extensible Message Parser"@1.0.2 + "SparkFun Extensible Message Parser"@1.0.4 "SparkFun BQ40Z50 Battery Manager Arduino Library"@1.0.0 "ArduinoMqttClient"@0.1.8 "SparkFun u-blox PointPerfect Library"@1.11.4 "SparkFun IM19 IMU Arduino Library"@1.0.1 - "SparkFun UM980 Triband RTK GNSS Arduino Library"@1.0.4 + "SparkFun UM980 Triband RTK GNSS Arduino Library"@1.0.5 "SparkFun LG290P Quadband RTK GNSS Arduino Library"@1.0.8 "SparkFun I2C Expander Arduino Library"@1.0.1 diff --git a/.github/workflows/non-release-build.yml b/.github/workflows/non-release-build.yml index 7756986d8..d9db34303 100644 --- a/.github/workflows/non-release-build.yml +++ b/.github/workflows/non-release-build.yml @@ -88,13 +88,13 @@ jobs: "SparkFun u-blox GNSS v3"@3.1.8 "SparkFun Qwiic OLED Arduino Library"@1.0.13 SSLClientESP32@2.0.0 - "SparkFun Extensible Message Parser"@1.0.2 + "SparkFun Extensible Message Parser"@1.0.4 "SparkFun BQ40Z50 Battery Manager Arduino Library"@1.0.0 "ArduinoMqttClient"@0.1.8 "SparkFun u-blox PointPerfect Library"@1.11.4 "SparkFun IM19 IMU Arduino Library"@1.0.1 - "SparkFun UM980 Triband RTK GNSS Arduino Library"@1.0.4 - "SparkFun LG290P Quadband RTK GNSS Arduino Library"@1.0.7 + "SparkFun UM980 Triband RTK GNSS Arduino Library"@1.0.5 + "SparkFun LG290P Quadband RTK GNSS Arduino Library"@1.0.8 "SparkFun I2C Expander Arduino Library"@1.0.1 - name: Patch libmbedtls diff --git a/Firmware/RTK_Everywhere/AP-Config/index.html b/Firmware/RTK_Everywhere/AP-Config/index.html index beadeb547..5ca7044ad 100644 --- a/Firmware/RTK_Everywhere/AP-Config/index.html +++ b/Firmware/RTK_Everywhere/AP-Config/index.html @@ -2016,7 +2016,7 @@ @@ -2027,15 +2027,23 @@
-

+
+ + + + + +
+
diff --git a/Firmware/RTK_Everywhere/AP-Config/src/main.js b/Firmware/RTK_Everywhere/AP-Config/src/main.js index 5552ae69a..50822d42c 100644 --- a/Firmware/RTK_Everywhere/AP-Config/src/main.js +++ b/Firmware/RTK_Everywhere/AP-Config/src/main.js @@ -1004,8 +1004,8 @@ function validateFields() { //System Config if (ge("enableLogging").checked == true) { - checkElementValue("maxLogTime", 1, 1051200, "Must be 1 to 1,051,200", "collapseSystemConfig"); - checkElementValue("maxLogLength", 1, 1051200, "Must be 1 to 1,051,200", "collapseSystemConfig"); + checkElementValue("maxLogTime", 0, 1051200, "Must be 0 to 1,051,200", "collapseSystemConfig"); + checkElementValue("maxLogLength", 0, 2880, "Must be 0 to 2880", "collapseSystemConfig"); } else { clearElement("maxLogTime", 60 * 24); diff --git a/Firmware/RTK_Everywhere/Begin.ino b/Firmware/RTK_Everywhere/Begin.ino index ed582782d..c5ad98578 100644 --- a/Firmware/RTK_Everywhere/Begin.ino +++ b/Firmware/RTK_Everywhere/Begin.ino @@ -843,10 +843,10 @@ void beginSD() gotSemaphore = false; - while (settings.enableSD == true) + while (settings.enableSD == true) // Note: settings.enableSD is never set to false { // Setup SD card access semaphore - if (sdCardSemaphore == nullptr) + if (sdCardSemaphore == NULL) sdCardSemaphore = xSemaphoreCreateMutex(); else if (xSemaphoreTake(sdCardSemaphore, fatSemaphore_shortWait_ms) != pdPASS) { @@ -862,7 +862,7 @@ void beginSD() break; // Give up on loop // If an SD card is present, allow SdFat to take over - log_d("SD card detected"); + systemPrintf("SD card detected @ %s\r\n", getTimeStamp()); // Allocate the data structure that manages the microSD card if (!sd) @@ -930,7 +930,7 @@ void beginSD() sdCardSize = 0; outOfSDSpace = true; - systemPrintln("microSD: Online"); + systemPrintf("microSD: Online @ %s\r\n", getTimeStamp()); online.microSD = true; break; } @@ -951,7 +951,7 @@ void endSD(bool alreadyHaveSemaphore, bool releaseSemaphore) sd->end(); online.microSD = false; - systemPrintln("microSD: Offline"); + systemPrintf("microSD: Offline @ %s\r\n", getTimeStamp()); } // Free the caches for the microSD card @@ -1699,7 +1699,7 @@ void tpISR() { if (online.rtc) // Only sync if the RTC has been set via PVT first { - if (timTpUpdated) // Only sync if timTpUpdated is true + if (timTpUpdated) // Only sync if timTpUpdated is true - set by storeTIMTPdata on ZED platforms only { if (millisNow - lastRTCSync > syncRTCInterval) // Only sync if it is more than syncRTCInterval since the last sync diff --git a/Firmware/RTK_Everywhere/Buttons.ino b/Firmware/RTK_Everywhere/Buttons.ino index 76c240300..3a6a7d644 100644 --- a/Firmware/RTK_Everywhere/Buttons.ino +++ b/Firmware/RTK_Everywhere/Buttons.ino @@ -119,7 +119,7 @@ void buttonRead() gpioChanged = false; // Get all the pins in one read - uint8_t currentState = io.getInputRegister() & 0b00111111; // Ignore unconnected GPIO6/7 + uint8_t currentState = io.getInputRegister() & 0b00011111; // Mask the five buttons. Ignore SD detect if (currentState != gpioExpander_previousState) { diff --git a/Firmware/RTK_Everywhere/ESPNOW.ino b/Firmware/RTK_Everywhere/ESPNOW.ino index 3a444e985..b725a4a84 100644 --- a/Firmware/RTK_Everywhere/ESPNOW.ino +++ b/Firmware/RTK_Everywhere/ESPNOW.ino @@ -161,6 +161,7 @@ void espNowOnDataReceived(const esp_now_recv_info *mac, const uint8_t *incomingD { // Pass RTCM bytes (presumably) from ESP NOW out ESP32-UART to GNSS gnss->pushRawData((uint8_t *)incomingData, len); + sempParseNextBytes(rtcmParse, (uint8_t *)incomingData, len); // Parse the data for RTCM1005/1006 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/GNSS.ino b/Firmware/RTK_Everywhere/GNSS.ino index d4cc47ec9..09719b908 100644 --- a/Firmware/RTK_Everywhere/GNSS.ino +++ b/Firmware/RTK_Everywhere/GNSS.ino @@ -72,29 +72,52 @@ bool GNSS::supportsAntennaShortOpen() } // Periodically push GGA sentence over NTRIP Client, to Caster, if enabled -void pushGPGGA(char *ggaData) +// We must not push to the Caster while we are reading data from the Caster +// See #695 +// pushGPGGA is called by processUart1Message from gnssReadTask +// ntripClient->read is called by ntripClientUpdate and ntripClientResponse from networkUpdate from loop +// We need to make sure processUart1Message doesn't gatecrash +// If ggaData is provided, store it. If ggaData is nullptr, try to push it +static void pushGPGGA(char *ggaData) { -#ifdef COMPILE_NETWORK - // Wait until the client has been created - if (ntripClient != nullptr) + static char storedGPGGA[100]; + + static SemaphoreHandle_t reentrant = xSemaphoreCreateMutex(); // Create the mutex + + if (xSemaphoreTake(reentrant, 10 / portTICK_PERIOD_MS) == pdPASS) { - // Provide the caster with our current position as needed - if (ntripClient->connected() && settings.ntripClient_TransmitGGA == true) + if (ggaData) { - if (millis() - lastGGAPush > NTRIPCLIENT_MS_BETWEEN_GGA) - { - lastGGAPush = millis(); + snprintf(storedGPGGA, sizeof(storedGPGGA), "%s", ggaData); + xSemaphoreGive(reentrant); + return; + } - if (settings.debugNtripClientRtcm || PERIODIC_DISPLAY(PD_NTRIP_CLIENT_GGA)) +#ifdef COMPILE_NETWORK + // Wait until the client has been created + if (ntripClient != nullptr) + { + // Provide the caster with our current position as needed + if (ntripClient->connected() && settings.ntripClient_TransmitGGA == true) + { + if (millis() - lastGGAPush > NTRIPCLIENT_MS_BETWEEN_GGA) { - PERIODIC_CLEAR(PD_NTRIP_CLIENT_GGA); - systemPrintf("NTRIP Client pushing GGA to server: %s", (const char *)ggaData); - } + lastGGAPush = millis(); - // Push our current GGA sentence to caster - ntripClient->print((const char *)ggaData); + if ((settings.debugNtripClientRtcm || PERIODIC_DISPLAY(PD_NTRIP_CLIENT_GGA)) && !inMainMenu) + { + PERIODIC_CLEAR(PD_NTRIP_CLIENT_GGA); + systemPrintf("NTRIP Client pushing GGA to server: %s", (const char *)storedGPGGA); + } + + // Push our current GGA sentence to caster + if (strlen(storedGPGGA) > 0) + ntripClient->write((const uint8_t *)storedGPGGA, strlen(storedGPGGA)); + } } } - } #endif // COMPILE_NETWORK + + xSemaphoreGive(reentrant); + } } diff --git a/Firmware/RTK_Everywhere/GNSS_ZED.ino b/Firmware/RTK_Everywhere/GNSS_ZED.ino index 4f1533373..09d5a08d7 100644 --- a/Firmware/RTK_Everywhere/GNSS_ZED.ino +++ b/Firmware/RTK_Everywhere/GNSS_ZED.ino @@ -542,10 +542,6 @@ bool GNSS_ZED::configureGNSS() _zed->setAutoPVTcallbackPtr(&storePVTdata, VAL_LAYER_ALL); // Enable automatic NAV PVT messages with callback to storePVTdata response &= _zed->setAutoHPPOSLLHcallbackPtr( &storeHPdata, VAL_LAYER_ALL); // 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) response &= _zed->setAutoTIMTPcallbackPtr( @@ -2598,30 +2594,6 @@ void GNSS_ZED::storeMONCOMMSdataRadio(UBX_MON_COMMS_data_t *ubxDataStruct) } } -//---------------------------------------- -// 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) { diff --git a/Firmware/RTK_Everywhere/LoRa.ino b/Firmware/RTK_Everywhere/LoRa.ino index 7901fc3a7..0141ef48a 100644 --- a/Firmware/RTK_Everywhere/LoRa.ino +++ b/Firmware/RTK_Everywhere/LoRa.ino @@ -175,6 +175,9 @@ void updateLora() // Pass RTCM bytes (presumably) from LoRa out ESP32-UART to GNSS gnss->pushRawData(rtcmData, rtcmCount); // Push RTCM to GNSS module + // Parse the data for RTCM1005/1006 + sempParseNextBytes(rtcmParse, rtcmData, rtcmCount); + if (((settings.debugCorrections == true) || (settings.debugLora == true)) && !inMainMenu) { systemFlush(); // Complete prints @@ -254,6 +257,9 @@ void updateLora() // Pass RTCM bytes (presumably) from LoRa out ESP32-UART to GNSS gnss->pushRawData(rtcmData, rtcmCount); // Push RTCM to GNSS module + // Parse the data for RTCM1005/1006 + sempParseNextBytes(rtcmParse, rtcmData, rtcmCount); + if (((settings.debugCorrections == true) || (settings.debugLora == true)) && !inMainMenu) { systemFlush(); // Complete prints diff --git a/Firmware/RTK_Everywhere/MQTT_Client.ino b/Firmware/RTK_Everywhere/MQTT_Client.ino index 90ef48162..71081001c 100644 --- a/Firmware/RTK_Everywhere/MQTT_Client.ino +++ b/Firmware/RTK_Everywhere/MQTT_Client.ino @@ -514,6 +514,7 @@ int mqttClientProcessZedMessage(uint8_t *mqttData, uint16_t mqttCount, int bytes zed->updateCorrectionsSource(0); // Set SOURCE to 0 (IP) if needed gnss->pushRawData(mqttData, mqttCount); + // Corrections are SPARTN. No point in pushing them to rtcmParse bytesPushed += mqttCount; mqttClientDataReceived = true; @@ -542,6 +543,7 @@ int mqttClientProcessZedMessage(uint8_t *mqttData, uint16_t mqttCount, int bytes } gnss->pushRawData(mqttData, mqttCount); + // No point in pushing keys / MGA to rtcmParse bytesPushed += mqttCount; } #endif // COMPILE_ZED diff --git a/Firmware/RTK_Everywhere/NtripClient.ino b/Firmware/RTK_Everywhere/NtripClient.ino index a686367d6..8bc8d8097 100644 --- a/Firmware/RTK_Everywhere/NtripClient.ino +++ b/Firmware/RTK_Everywhere/NtripClient.ino @@ -138,7 +138,7 @@ static const uint32_t NTRIP_CLIENT_RESPONSE_TIMEOUT = 10 * 1000; // Milliseconds static const uint32_t NTRIP_CLIENT_RECEIVE_DATA_TIMEOUT = 30 * 1000; // Milliseconds // Most incoming data is around 500 bytes but may be larger -static const int RTCM_DATA_SIZE = 512 * 4; +static const size_t RTCM_DATA_SIZE = 512 * 4; // NTRIP client server request buffer size static const int SERVER_BUFFER_SIZE = CREDENTIALS_BUFFER_SIZE + 3; @@ -228,7 +228,7 @@ bool ntripClientConnect() systemPrintf("NTRIP Client connecting to %s:%d\r\n", settings.ntripClient_CasterHost, settings.ntripClient_CasterPort); - int connectResponse = ntripClient->connect(settings.ntripClient_CasterHost, settings.ntripClient_CasterPort); + int connectResponse = ntripClient->connect(settings.ntripClient_CasterHost, settings.ntripClient_CasterPort, NTRIP_CLIENT_RESPONSE_TIMEOUT); if (connectResponse < 1) { @@ -248,7 +248,7 @@ bool ntripClientConnect() length = strlen(serverRequest); serverRequest[length++] = '\r'; serverRequest[length++] = '\n'; - serverRequest[length++] = 0; + serverRequest[length] = 0; // Set up the credentials char credentials[CREDENTIALS_BUFFER_SIZE]; @@ -657,7 +657,8 @@ void ntripClientUpdate() { // Allocate the ntripClient structure networkUseDefaultInterface(); - ntripClient = new NetworkClient(); + if (!ntripClient) + ntripClient = new NetworkClient(); if (!ntripClient) { // Failed to allocate the ntripClient structure @@ -803,6 +804,7 @@ void ntripClientUpdate() // We don't use a task because we use I2C hardware (and don't have a semaphore). online.ntripClient = true; ntripClientStartTime = millis(); + ntripClient->setConnectionTimeout(NTRIP_CLIENT_RECEIVE_DATA_TIMEOUT); ntripClientSetState(NTRIP_CLIENT_CONNECTED); } } @@ -919,9 +921,10 @@ void ntripClientUpdate() { // Push RTCM to GNSS module over I2C / SPI gnss->pushRawData(rtcmData, rtcmCount); + sempParseNextBytes(rtcmParse, rtcmData, rtcmCount); // Parse the data for RTCM1005/1006 if ((settings.debugCorrections || settings.debugNtripClientRtcm || - PERIODIC_DISPLAY(PD_NTRIP_CLIENT_DATA)) && + PERIODIC_DISPLAY(PD_NTRIP_CLIENT_DATA)) && (!inMainMenu)) { PERIODIC_CLEAR(PD_NTRIP_CLIENT_DATA); @@ -931,7 +934,7 @@ void ntripClientUpdate() else { if ((settings.debugCorrections || settings.debugNtripClientRtcm || - PERIODIC_DISPLAY(PD_NTRIP_CLIENT_DATA)) && + PERIODIC_DISPLAY(PD_NTRIP_CLIENT_DATA)) && (!inMainMenu)) { PERIODIC_CLEAR(PD_NTRIP_CLIENT_DATA); @@ -943,6 +946,9 @@ void ntripClientUpdate() } } } + + // Now that the ntripClient->read is complete, write GPGGA if needed and available. See #695 + pushGPGGA(nullptr); } break; } diff --git a/Firmware/RTK_Everywhere/NtripServer.ino b/Firmware/RTK_Everywhere/NtripServer.ino index 85a5417e5..c8698fd62 100644 --- a/Firmware/RTK_Everywhere/NtripServer.ino +++ b/Firmware/RTK_Everywhere/NtripServer.ino @@ -173,7 +173,7 @@ const RtkMode_t ntripServerMode = RTK_MODE_BASE_FIXED; //---------------------------------------- // NTRIP Servers -static NTRIP_SERVER_DATA ntripServerArray[NTRIP_SERVER_MAX]; +volatile static NTRIP_SERVER_DATA ntripServerArray[NTRIP_SERVER_MAX]; //---------------------------------------- // NTRIP Server Routines @@ -184,7 +184,7 @@ static NTRIP_SERVER_DATA ntripServerArray[NTRIP_SERVER_MAX]; //---------------------------------------- bool ntripServerConnectCaster(int serverIndex) { - NTRIP_SERVER_DATA *ntripServer = &ntripServerArray[serverIndex]; + volatile NTRIP_SERVER_DATA *ntripServer = &ntripServerArray[serverIndex]; const int SERVER_BUFFER_SIZE = 512; char serverBuffer[SERVER_BUFFER_SIZE]; @@ -239,7 +239,7 @@ bool ntripServerConnectLimitReached(int serverIndex) { bool limitReached; int minutes; - NTRIP_SERVER_DATA *ntripServer = &ntripServerArray[serverIndex]; + volatile NTRIP_SERVER_DATA *ntripServer = &ntripServerArray[serverIndex]; int seconds; // Retry the connection a few times @@ -249,8 +249,8 @@ bool ntripServerConnectLimitReached(int serverIndex) // Shutdown the NTRIP server ntripServerStop(serverIndex, limitReached || (!ntripServerEnabled(serverIndex, nullptr))); - ntripServer->connectionAttempts++; - ntripServer->connectionAttemptsTotal++; + ntripServer->connectionAttempts = ntripServer->connectionAttempts + 1; + ntripServer->connectionAttemptsTotal = ntripServer->connectionAttemptsTotal + 1; if (settings.debugNtripServerState) ntripServerPrintStatus(serverIndex); @@ -334,7 +334,7 @@ bool ntripServerEnabled(int serverIndex, const char ** line) //---------------------------------------- void ntripServerPrintStateSummary(int serverIndex) { - NTRIP_SERVER_DATA *ntripServer = &ntripServerArray[serverIndex]; + volatile NTRIP_SERVER_DATA *ntripServer = &ntripServerArray[serverIndex]; switch (ntripServer->state) { @@ -362,7 +362,7 @@ void ntripServerPrintStateSummary(int serverIndex) //---------------------------------------- void ntripServerPrintStatus(int serverIndex) { - NTRIP_SERVER_DATA *ntripServer = &ntripServerArray[serverIndex]; + volatile NTRIP_SERVER_DATA *ntripServer = &ntripServerArray[serverIndex]; uint64_t milliseconds; uint32_t days; byte hours; @@ -410,7 +410,7 @@ void ntripServerPrintStatus(int serverIndex) //---------------------------------------- void ntripServerProcessRTCM(int serverIndex, uint8_t incoming) { - NTRIP_SERVER_DATA *ntripServer = &ntripServerArray[serverIndex]; + volatile NTRIP_SERVER_DATA *ntripServer = &ntripServerArray[serverIndex]; if (ntripServer->state == NTRIP_SERVER_CASTING) { @@ -425,14 +425,7 @@ void ntripServerProcessRTCM(int serverIndex, uint8_t incoming) (!settings.enableRtcmMessageChecking) && (!inMainMenu) && ntripServer->bytesSent) { PERIODIC_CLEAR(PD_NTRIP_SERVER_DATA); - printTimeStamp(); - // 1 2 3 - // 123456789012345678901234567890 - // YYYY-mm-dd HH:MM:SS.xxxrn0 - struct tm timeinfo = rtc.getTimeStruct(); - char timestamp[30]; - strftime(timestamp, sizeof(timestamp), "%Y-%m-%d %H:%M:%S", &timeinfo); - systemPrintf(" Tx%d RTCM: %s.%03ld, %d bytes sent\r\n", serverIndex, timestamp, rtc.getMillis(), + systemPrintf(" Tx%d RTCM: %s, %d bytes sent\r\n", serverIndex, getTimeStamp(), ntripServer->rtcmBytesSent); ntripServer->rtcmBytesSent = 0; } @@ -452,8 +445,8 @@ void ntripServerProcessRTCM(int serverIndex, uint8_t incoming) if (ntripServer->networkClient && ntripServer->networkClient->connected()) { ntripServer->networkClient->write(incoming); // Send this byte to socket - ntripServer->bytesSent++; - ntripServer->rtcmBytesSent++; + ntripServer->bytesSent = ntripServer->bytesSent + 1; + ntripServer->rtcmBytesSent = ntripServer->rtcmBytesSent + 1; ntripServer->timer = millis(); netOutgoingRTCM = true; } @@ -471,7 +464,7 @@ void ntripServerProcessRTCM(int serverIndex, uint8_t incoming) //---------------------------------------- void ntripServerResponse(int serverIndex, char *response, size_t maxLength) { - NTRIP_SERVER_DATA *ntripServer = &ntripServerArray[serverIndex]; + volatile NTRIP_SERVER_DATA *ntripServer = &ntripServerArray[serverIndex]; char *responseEnd; // Make sure that we can zero terminate the response @@ -490,7 +483,7 @@ void ntripServerResponse(int serverIndex, char *response, size_t maxLength) //---------------------------------------- void ntripServerRestart(int serverIndex) { - NTRIP_SERVER_DATA *ntripServer = &ntripServerArray[serverIndex]; + volatile NTRIP_SERVER_DATA *ntripServer = &ntripServerArray[serverIndex]; // Save the previous uptime value if (ntripServer->state == NTRIP_SERVER_CASTING) @@ -503,7 +496,7 @@ void ntripServerRestart(int serverIndex) //---------------------------------------- void ntripServerSetState(int serverIndex, uint8_t newState) { - NTRIP_SERVER_DATA * ntripServer; + volatile NTRIP_SERVER_DATA * ntripServer; ntripServer = &ntripServerArray[serverIndex]; if (settings.debugNtripServerState) @@ -555,7 +548,7 @@ void ntripServerStop(int serverIndex, bool shutdown) { bool enabled; int index; - NTRIP_SERVER_DATA *ntripServer = &ntripServerArray[serverIndex]; + volatile NTRIP_SERVER_DATA *ntripServer = &ntripServerArray[serverIndex]; if (ntripServer->networkClient) { @@ -619,7 +612,7 @@ void ntripServerUpdate(int serverIndex) const char * line = ""; // Get the NTRIP data structure - NTRIP_SERVER_DATA *ntripServer = &ntripServerArray[serverIndex]; + volatile NTRIP_SERVER_DATA *ntripServer = &ntripServerArray[serverIndex]; // Shutdown the NTRIP server when the mode or setting changes DMW_if diff --git a/Firmware/RTK_Everywhere/PointPerfectLibrary.ino b/Firmware/RTK_Everywhere/PointPerfectLibrary.ino index 1116dd4ca..93e3175ec 100644 --- a/Firmware/RTK_Everywhere/PointPerfectLibrary.ino +++ b/Firmware/RTK_Everywhere/PointPerfectLibrary.ino @@ -48,6 +48,7 @@ void updatePplTask(void *e) } gnss->pushRawData(pplRtcmBuffer, rtcmLength); + sempParseNextBytes(rtcmParse, pplRtcmBuffer, rtcmLength); // Parse the data for RTCM1005/1006 if (settings.debugCorrections == true && !inMainMenu) systemPrintf("Received %d RTCM bytes from PPL. Pushed to the GNSS.\r\n", rtcmLength); diff --git a/Firmware/RTK_Everywhere/RTK_Everywhere.ino b/Firmware/RTK_Everywhere/RTK_Everywhere.ino index 43627718f..c4b71e954 100644 --- a/Firmware/RTK_Everywhere/RTK_Everywhere.ino +++ b/Firmware/RTK_Everywhere/RTK_Everywhere.ino @@ -307,6 +307,7 @@ const int COMMON_COORDINATES_MAX_STATIONS = 50; // Record up to 50 ECEF and Geod #include //http://librarymanager/All#ESP32Time by FBiego ESP32Time rtc; unsigned long syncRTCInterval = 1000; // To begin, sync RTC every second. Interval can be increased once sync'd. +void printTimeStamp(bool always = false); // Header //-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- // microSD Interface @@ -315,6 +316,8 @@ unsigned long syncRTCInterval = 1000; // To begin, sync RTC every second. Interv void beginSPI(bool force = false); // Header +// Important note: the firmware currently requires SdFat v2.1.1 +// sd->begin will crash second time around with ~v2.2.3 #include "SdFat.h" //http://librarymanager/All#sdfat_exfat by Bill Greiman. SdFat *sd; @@ -322,16 +325,28 @@ SdFat *sd; SdFile *logFile; // File that all GNSS messages sentences are written to unsigned long lastUBXLogSyncTime; // Used to record to SD every half second -int startLogTime_minutes; // Mark when we start any logging so we can stop logging after maxLogTime_minutes -int startCurrentLogTime_minutes; -// Mark when we start this specific log file so we can close it after x minutes and start a new one +int startLogTime_minutes; // Mark when we (re)start any logging so we can stop logging after maxLogTime_minutes +unsigned long nextLogTime_ms; // Open the next log file at this many millis() // System crashes if two tasks access a file at the same time // So we use a semaphore to see if the file system is available -SemaphoreHandle_t sdCardSemaphore; +// https://github.com/espressif/arduino-esp32/blob/master/libraries/ESP32/examples/FreeRTOS/Mutex/Mutex.ino#L11 +SemaphoreHandle_t sdCardSemaphore = NULL; TickType_t loggingSemaphoreWait_ms = 10 / portTICK_PERIOD_MS; const TickType_t fatSemaphore_shortWait_ms = 10 / portTICK_PERIOD_MS; const TickType_t fatSemaphore_longWait_ms = 200 / portTICK_PERIOD_MS; +const TickType_t ringBuffer_shortWait_ms = 20 / portTICK_PERIOD_MS; +const TickType_t ringBuffer_longWait_ms = 300 / portTICK_PERIOD_MS; + +// ringBuffer semaphore - prevent processUart1Message (gnssReadTask) and handleGnssDataTask +// from gatecrashing each other. +SemaphoreHandle_t ringBufferSemaphore = NULL; +const char *ringBufferSemaphoreHolder = "None"; + +// tcpServer semaphore - prevent tcpServerClientSendData (handleGnssDataTask) and tcpServerUpdate +// from gatecrashing each other. See #695 for why this is needed. +SemaphoreHandle_t tcpServerSemaphore = NULL; +const char *tcpServerSemaphoreHolder = "None"; // Display used/free space in menu and config page uint64_t sdCardSize; @@ -460,7 +475,7 @@ uint32_t timTpEpoch; uint32_t timTpMicros; unsigned long lastARPLog; // Time of the last ARP log event -bool newARPAvailable; +bool newARPAvailable = false; int64_t ARPECEFX; // ARP ECEF is 38-bit signed int64_t ARPECEFY; int64_t ARPECEFZ; @@ -472,6 +487,7 @@ unsigned long rtcmLastPacketReceived; // Time stamp of RTCM coming in (from BT, bool usbSerialIncomingRtcm; // Incoming RTCM over the USB serial port #define RTCM_CORRECTION_INPUT_TIMEOUT (2 * 1000) + //=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= // Extensible Message Parser @@ -480,6 +496,7 @@ bool usbSerialIncomingRtcm; // Incoming RTCM over the USB serial port SEMP_PARSE_STATE *rtkParse = nullptr; SEMP_PARSE_STATE *sbfParse = nullptr; // mosaic-X5 SEMP_PARSE_STATE *spartnParse = nullptr; // mosaic-X5 +SEMP_PARSE_STATE *rtcmParse = nullptr; // Parse incoming corrections for RTCM1005 / 1006 base locations //=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= @@ -806,7 +823,7 @@ std::vector setupButtons; // A vector (linked list) of the setup 'b bool firstRoverStart; // Used to detect if the user is toggling the power button at POR to enter the test menu -bool newEventToRecord; // Goes true when INT pin goes high +bool newEventToRecord; // Goes true when INT pin goes high. Currently this is ZED-specific. uint32_t triggerCount; // Global copy - TM2 event counter uint32_t triggerTowMsR; // Global copy - Time Of Week of rising edge (ms) uint32_t triggerTowSubMsR; // Global copy - Millisecond fraction of Time Of Week of rising edge in nanoseconds @@ -1222,6 +1239,9 @@ void setup() beginVersion(); // Assemble platform name. Requires settings/LFS. + if ((settings.haltOnPanic) && (esp_reset_reason() == ESP_RST_PANIC)) // Halt on PANIC - to trap rare crashes + reportFatalError("ESP_RST_PANIC"); + DMW_b("beginGnssUart"); beginGnssUart(); // Requires settings. Start the UART connected to the GNSS receiver on core 0. Start before // gnssBegin in case it is needed (Torch). @@ -1229,12 +1249,18 @@ void setup() DMW_b("beginGnssUart2"); beginGnssUart2(); + DMW_b("beginRtcmParse"); + beginRtcmParse(); + DMW_b("displaySplash"); displaySplash(); // Display the RTK product name and firmware version DMW_b("gnss->begin"); gnss->begin(); // Requires settings. Connect to GNSS to get module type + DMW_b("beginButtons"); + beginButtons(); // Start task for button monitoring. Needed for beginSD (gpioExpander) + DMW_b("beginSD"); beginSD(); // Requires settings. Test if SD is present @@ -1279,9 +1305,6 @@ void setup() DMW_b("beginInterrupts"); beginInterrupts(); // Begin the TP interrupts - DMW_b("beginButtons"); - beginButtons(); // Start task for button monitoring. - DMW_b("beginSystemState"); beginSystemState(); // Determine initial system state. @@ -1437,6 +1460,25 @@ void loopDelay() delay(10); } +bool logTimeExceeded() // Limit total logging time to maxLogTime_minutes +{ + if (settings.maxLogTime_minutes == 0) // No limit if maxLogTime_minutes is zero + return false; + + return ((systemTime_minutes - startLogTime_minutes) >= settings.maxLogTime_minutes); +} + +bool logLengthExceeded() // Limit individual files to maxLogLength_minutes +{ + if (settings.maxLogLength_minutes == 0) // No limit if maxLogLength_minutes is zero + return false; + + if (nextLogTime_ms == 0) // Keep logging if nextLogTime_ms has not been set + return false; + + return (millis() >= nextLogTime_ms); // Note: this will roll over every ~50 days... +} + // Create or close files as needed (startup or as the user changes settings) // Push new data to log as needed void logUpdate() @@ -1458,7 +1500,7 @@ void logUpdate() if (outOfSDSpace == true) return; // We can't log if we are out of SD space - if (online.logging == false && settings.enableLogging == true && blockLogging == false) + if (online.logging == false && settings.enableLogging == true && blockLogging == false && !logTimeExceeded()) { if (beginLogging() == false) { @@ -1474,93 +1516,24 @@ void logUpdate() // Close down file endSD(false, true); } - else if (online.logging == true && settings.enableLogging == true && - (systemTime_minutes - startCurrentLogTime_minutes) >= settings.maxLogLength_minutes) - { - endSD(false, true); // Close down file. A new one will be created at the next calling of updateLogs(). - } - - if (online.logging == true) + else if (online.logging == true && settings.enableLogging == true && (logLengthExceeded() || logTimeExceeded())) { - // Record any pending trigger events - if (newEventToRecord == true) + if (logTimeExceeded()) { - systemPrintln("Recording event"); - - // Record trigger count with Time Of Week of rising edge (ms), Millisecond fraction of Time Of Week of - // rising edge (ns), and accuracy estimate (ns) - char eventData[82]; // Max NMEA sentence length is 82 - snprintf(eventData, sizeof(eventData), "%d,%d,%d,%d", triggerCount, triggerTowMsR, triggerTowSubMsR, - triggerAccEst); - - char nmeaMessage[82]; // Max NMEA sentence length is 82 - createNMEASentence(CUSTOM_NMEA_TYPE_EVENT, nmeaMessage, sizeof(nmeaMessage), - eventData); // textID, buffer, sizeOfBuffer, text - - if (xSemaphoreTake(sdCardSemaphore, fatSemaphore_shortWait_ms) == pdPASS) - { - markSemaphore(FUNCTION_EVENT); - - logFile->println(nmeaMessage); - - xSemaphoreGive(sdCardSemaphore); - newEventToRecord = false; - } - else - { - char semaphoreHolder[50]; - getSemaphoreFunction(semaphoreHolder); - - // While a retry does occur during the next loop, it is possible to lose - // trigger events if they occur too rapidly or if the log file is closed - // before the trigger event is written! - log_w("sdCardSemaphore failed to yield, held by %s, RTK_Everywhere.ino line %d", semaphoreHolder, - __LINE__); - } + systemPrintln("Log file: maximum logging time reached"); + endSD(false, true); // Close down SD. } - - // Record the Antenna Reference Position - if available - if (newARPAvailable == true && settings.enableARPLogging && - ((millis() - lastARPLog) > (settings.ARPLoggingInterval_s * 1000))) + else { - systemPrintln("Recording Antenna Reference Position"); - - lastARPLog = millis(); - newARPAvailable = false; - - double x = ARPECEFX; - x /= 10000.0; // Convert to m - double y = ARPECEFY; - y /= 10000.0; // Convert to m - double z = ARPECEFZ; - z /= 10000.0; // Convert to m - double h = ARPECEFH; - h /= 10000.0; // Convert to m - char ARPData[82]; // Max NMEA sentence length is 82 - snprintf(ARPData, sizeof(ARPData), "%.4f,%.4f,%.4f,%.4f", x, y, z, h); - - char nmeaMessage[82]; // Max NMEA sentence length is 82 - createNMEASentence(CUSTOM_NMEA_TYPE_ARP_ECEF_XYZH, nmeaMessage, sizeof(nmeaMessage), - ARPData); // textID, buffer, sizeOfBuffer, text - - if (xSemaphoreTake(sdCardSemaphore, fatSemaphore_shortWait_ms) == pdPASS) - { - markSemaphore(FUNCTION_EVENT); - - logFile->println(nmeaMessage); - - xSemaphoreGive(sdCardSemaphore); - newEventToRecord = false; - } - else - { - char semaphoreHolder[50]; - getSemaphoreFunction(semaphoreHolder); - log_w("sdCardSemaphore failed to yield, held by %s, RTK_Everywhere.ino line %d", semaphoreHolder, - __LINE__); - } + systemPrintln("Log file: log length reached"); + endLogging(false, true); //(gotSemaphore, releaseSemaphore) Close file. Reset parser stats. + beginLogging(); // Create new file based on current RTC. + setLoggingType(); // Determine if we are standard, PPP, or custom. Changes logging icon accordingly. } + } + if (online.logging == true) + { // Report file sizes to show recording is working if ((millis() - lastFileReport) > 5000) { @@ -1572,7 +1545,7 @@ void logUpdate() { systemPrintf("Log file size: %lld", logFileSize); - if ((systemTime_minutes - startLogTime_minutes) < settings.maxLogTime_minutes) + if (!logTimeExceeded()) { // Calculate generation and write speeds every 5 seconds uint64_t fileSizeDelta = logFileSize - lastLogSize; @@ -1807,5 +1780,8 @@ void getSemaphoreFunction(char *functionName) case FUNCTION_NTPEVENT: strcpy(functionName, "NTP Event"); break; + case FUNCTION_ARPWRITE: + strcpy(functionName, "ARP Write"); + break; } } diff --git a/Firmware/RTK_Everywhere/SD.ino b/Firmware/RTK_Everywhere/SD.ino index 2cf8e48c7..09d485d75 100644 --- a/Firmware/RTK_Everywhere/SD.ino +++ b/Firmware/RTK_Everywhere/SD.ino @@ -27,7 +27,7 @@ void sdUpdate() } else if (sdCardPresent() == true) // Poll card to see if a card is inserted { - systemPrintln("SD inserted"); + systemPrintf("SD inserted @ %s\r\n", getTimeStamp()); beginSD(); // Attempt to start SD } } @@ -35,7 +35,7 @@ void sdUpdate() if (online.logging == true && sdCardSize > 0 && sdFreeSpace < sdMinAvailableSpace) // Stop logging if we are below the min { - log_d("Logging stopped. SD full."); + systemPrintf("Logging stopped. SD full @ %s\r\n", getTimeStamp()); outOfSDSpace = true; endSD(false, true); //(alreadyHaveSemaphore, releaseSemaphore) Close down file. return; @@ -49,7 +49,10 @@ void sdUpdate() // Check if SD card is still present if (sdCardPresent() == false) + { + systemPrintf("SD removed @ %s\r\n", getTimeStamp()); endSD(false, true); //(alreadyHaveSemaphore, releaseSemaphore) Close down SD. + } } /* @@ -94,44 +97,75 @@ bool sdCardPresent(void) return (true); // Card detect high = SD in place return (false); // Card detect low = No SD } - else if (present.microSdCardDetectGpioExpanderHigh == true && online.gpioExpander == true) + else if (present.microSdCardDetectGpioExpanderHigh == true) { - if (io.digitalRead(gpioExpander_cardDetect) == GPIO_EXPANDER_CARD_INSERTED) - return (true); // Card detect high = SD in place - return (false); // Card detect low = No SD + if (online.gpioExpander == true) + { + if (io.digitalRead(gpioExpander_cardDetect) == GPIO_EXPANDER_CARD_INSERTED) + return (true); // Card detect high = SD in place + return (false); // Card detect low = No SD + } + else + { + reportFatalError("sdCardPresent: gpioExpander not online."); + return (false); + } } // else - no card detect pin. Use software detect - // Use software to detect a card - DMW_if systemPrintf("pin_microSD_CS: %d\r\n", pin_microSD_CS); - if (pin_microSD_CS == -1) - reportFatalError("Illegal SD CS pin assignment."); + // Note: even though this is protected by the semaphore, + // this will probably cause issues / corruption if + // a SdFile is open for writing...? + + static bool previousCardPresentBySW = false; + + if (xSemaphoreTake(sdCardSemaphore, fatSemaphore_longWait_ms) == pdPASS) + { + markSemaphore(FUNCTION_RECORDSETTINGS); + + // Use software to detect a card + DMW_if systemPrintf("pin_microSD_CS: %d\r\n", pin_microSD_CS); + if (pin_microSD_CS == -1) + reportFatalError("Illegal SD CS pin assignment."); + + byte response = 0; - byte response = 0; + beginSPI(false); + SPI.setClockDivider(SPI_CLOCK_DIV2); + SPI.setDataMode(SPI_MODE0); + SPI.setBitOrder(MSBFIRST); + pinMode(pin_microSD_CS, OUTPUT); - beginSPI(false); - SPI.setClockDivider(SPI_CLOCK_DIV2); - SPI.setDataMode(SPI_MODE0); - SPI.setBitOrder(MSBFIRST); - pinMode(pin_microSD_CS, OUTPUT); + // Sending clocks while card power stabilizes... + sdDeselectCard(); // always make sure + for (byte i = 0; i < 30; i++) // send several clocks while card power stabilizes + xchg(0xff); + + // Sending CMD0 - GO IDLE... + for (byte i = 0; i < 0x10; i++) // Attempt to go idle + { + response = sdSendCommand(SD_GO_IDLE, 0); // send CMD0 - go to idle state + if (response == 1) + break; + } - // Sending clocks while card power stabilizes... - sdDeselectCard(); // always make sure - for (byte i = 0; i < 30; i++) // send several clocks while card power stabilizes - xchg(0xff); + xSemaphoreGive(sdCardSemaphore); + + if (response != 1) + { + previousCardPresentBySW = false; + return (false); // Card failed to respond to idle + } - // Sending CMD0 - GO IDLE... - for (byte i = 0; i < 0x10; i++) // Attempt to go idle + previousCardPresentBySW = true; + return (true); // Card detected + } + else { - response = sdSendCommand(SD_GO_IDLE, 0); // send CMD0 - go to idle state - if (response == 1) - break; + // Could not get semaphore. Return previous state + return previousCardPresentBySW; } - if (response != 1) - return (false); // Card failed to respond to idle - - return (true); // Card detected } /* diff --git a/Firmware/RTK_Everywhere/States.ino b/Firmware/RTK_Everywhere/States.ino index 3154afb01..0cc059062 100644 --- a/Firmware/RTK_Everywhere/States.ino +++ b/Firmware/RTK_Everywhere/States.ino @@ -764,16 +764,8 @@ void changeState(SystemState newState) if (!online.rtc) systemPrintf("%s%s%s%s\r\n", asterisk, initialState, arrow, endingState); else - { // Timestamp the state change - // 1 2 - // 12345678901234567890123456 - // YYYY-mm-dd HH:MM:SS.xxxrn0 - struct tm timeinfo = rtc.getTimeStruct(); - char s[30]; - strftime(s, sizeof(s), "%Y-%m-%d %H:%M:%S", &timeinfo); - systemPrintf("%s%s%s%s, %s.%03ld\r\n", asterisk, initialState, arrow, endingState, s, rtc.getMillis()); - } + systemPrintf("%s%s%s%s, %s\r\n", asterisk, initialState, arrow, endingState, getTimeStamp()); } } diff --git a/Firmware/RTK_Everywhere/Tasks.ino b/Firmware/RTK_Everywhere/Tasks.ino index bbdeebbdf..9e330b2e8 100644 --- a/Firmware/RTK_Everywhere/Tasks.ino +++ b/Firmware/RTK_Everywhere/Tasks.ino @@ -94,6 +94,7 @@ const char *const sbfParserNames[] = { "SBF", }; const int sbfParserNameCount = sizeof(sbfParserNames) / sizeof(sbfParserNames[0]); + SEMP_PARSE_ROUTINE const spartnParserTable[] = {sempSpartnPreamble}; const int spartnParserCount = sizeof(spartnParserTable) / sizeof(spartnParserTable[0]); const char *const spartnParserNames[] = { @@ -101,6 +102,11 @@ const char *const spartnParserNames[] = { }; const int spartnParserNameCount = sizeof(spartnParserNames) / sizeof(spartnParserNames[0]); +SEMP_PARSE_ROUTINE const rtcmParserTable[] = { sempRtcmPreamble }; +const int rtcmParserCount = sizeof(rtcmParserTable) / sizeof(rtcmParserTable[0]); +const char *const rtcmParserNames[] = { "RTCM" }; +const int rtcmParserNameCount = sizeof(rtcmParserNames) / sizeof(rtcmParserNames[0]); + //---------------------------------------- // Locals //---------------------------------------- @@ -287,6 +293,7 @@ void sendGnssBuffer() { if (correctionLastSeen(CORR_BLUETOOTH)) { + sempParseNextBytes(rtcmParse, bluetoothOutgoingToGnss, bluetoothOutgoingToGnssHead); // Parse the data for RTCM1005/1006 if (gnss->pushRawData(bluetoothOutgoingToGnss, bluetoothOutgoingToGnssHead)) { if ((settings.debugCorrections || PERIODIC_DISPLAY(PD_GNSS_DATA_TX)) && !inMainMenu) @@ -779,222 +786,271 @@ void processUart1Message(SEMP_PARSE_STATE *parse, uint16_t type) parse->length = 0; } - // Determine if this message will fit into the ring buffer - bytesToCopy = parse->length; - space = availableHandlerSpace; - use = settings.gnssHandlerBufferSize - space; - consumer = (char *)slowConsumer; - if ((bytesToCopy > space) && (!inMainMenu)) + // If parse->length is zero, we should exit now. + // Previously, the code would continue past here and fill rbOffsetArray with 'empty' entries. + // E.g. RTCM1019/1042/1046 suppressed above + if (parse->length == 0) + return; + + // Use a semaphore to prevent handleGnssDataTask from gatecrashing + if (ringBufferSemaphore == NULL) + ringBufferSemaphore = xSemaphoreCreateMutex(); // Create the mutex + + // Take the semaphore. Long wait. handleGnssDataTask could block + // Enable printing of the ring buffer offsets (s d 10) and the SD buffer sizes (s h 7) + // to see this in action. No more gatecrashing! + if (xSemaphoreTake(ringBufferSemaphore, ringBuffer_longWait_ms) == pdPASS) { - int32_t bufferedData; - int32_t bytesToDiscard; - int32_t discardedBytes; - int32_t listEnd; - int32_t messageLength; - int32_t previousTail; - int32_t rbOffsetTail; - - // Determine the tail of the ring buffer - previousTail = dataHead + space + 1; - if (previousTail >= settings.gnssHandlerBufferSize) - previousTail -= settings.gnssHandlerBufferSize; - - /* The rbOffsetArray holds the offsets into the ring buffer of the - * start of each of the parsed messages. A head (rbOffsetHead) and - * tail (rbOffsetTail) offsets are used for this array to insert and - * remove entries. Typically this task only manipulates the head as - * new messages are placed into the ring buffer. The handleGnssDataTask - * normally manipulates the tail as data is removed from the buffer. - * However this task will manipulate the tail under two conditions: - * - * 1. The ring buffer gets full and data must be discarded - * - * 2. The rbOffsetArray is too small to hold all of the message - * offsets for the data in the ring buffer. The array is full - * when (Head + 1) == Tail - * - * Notes: - * The rbOffsetArray is allocated along with the ring buffer in - * Begin.ino - * - * The first entry rbOffsetArray[0] is initialized to zero (0) - * in Begin.ino - * - * The array always has one entry in it containing the head offset - * which contains a valid offset into the ringBuffer, handled below - * - * The empty condition is Tail == Head - * - * The amount of data described by the rbOffsetArray is - * rbOffsetArray[Head] - rbOffsetArray[Tail] - * - * rbOffsetArray ringBuffer - * .-----------------. .-----------------. - * | | | | - * +-----------------+ | | - * Tail --> | Msg 1 Offset |---------->+-----------------+ <-- Tail n - * +-----------------+ | Msg 1 | - * | Msg 2 Offset |--------. | | - * +-----------------+ | | | - * | Msg 3 Offset |------. '->+-----------------+ - * +-----------------+ | | Msg 2 | - * Head --> | Head Offset |--. | | | - * +-----------------+ | | | | - * | | | | | | - * +-----------------+ | | | | - * | | | '--->+-----------------+ - * +-----------------+ | | Msg 3 | - * | | | | | - * +-----------------+ '------->+-----------------+ <-- dataHead - * | | | | - */ - - // Determine the index for the end of the circular ring buffer - // offset list - listEnd = rbOffsetHead; - WRAP_OFFSET(listEnd, 1, rbOffsetEntries); - - // Update the tail, walk newest message to oldest message - rbOffsetTail = rbOffsetHead; - bufferedData = 0; - messageLength = 0; - while ((rbOffsetTail != listEnd) && (bufferedData < use)) + ringBufferSemaphoreHolder = "processUart1Message"; + + // Determine if this message will fit into the ring buffer + int32_t discardedBytes = 0; + bytesToCopy = parse->length; + space = availableHandlerSpace; // Take a copy of availableHandlerSpace here + use = settings.gnssHandlerBufferSize - space; + consumer = (char *)slowConsumer; + if (bytesToCopy > space) // Paul removed the && (!inMainMenu)) check 7-25-25 { - // Determine the amount of data in the ring buffer up until - // either the tail or the end of the rbOffsetArray + int32_t bufferedData; + int32_t bytesToDiscard; + int32_t listEnd; + int32_t messageLength; + int32_t previousTail; + int32_t rbOffsetTail; + + // Determine the tail of the ring buffer + previousTail = dataHead + space + 1; + if (previousTail >= settings.gnssHandlerBufferSize) + previousTail -= settings.gnssHandlerBufferSize; + + /* The rbOffsetArray holds the offsets into the ring buffer of the + * start of each of the parsed messages. A head (rbOffsetHead) and + * tail (rbOffsetTail) offsets are used for this array to insert and + * remove entries. Typically this task only manipulates the head as + * new messages are placed into the ring buffer. The handleGnssDataTask + * normally manipulates the tail as data is removed from the buffer. + * However this task will manipulate the tail under two conditions: + * + * 1. The ring buffer gets full and data must be discarded + * + * 2. The rbOffsetArray is too small to hold all of the message + * offsets for the data in the ring buffer. The array is full + * when (Head + 1) == Tail + * + * Notes: + * The rbOffsetArray is allocated along with the ring buffer in + * Begin.ino + * + * The first entry rbOffsetArray[0] is initialized to zero (0) + * in Begin.ino + * + * The array always has one entry in it containing the head offset + * which contains a valid offset into the ringBuffer, handled below + * + * The empty condition is Tail == Head + * + * The amount of data described by the rbOffsetArray is + * rbOffsetArray[Head] - rbOffsetArray[Tail] + * + * rbOffsetArray ringBuffer + * .-----------------. .-----------------. + * | | | | + * +-----------------+ | | + * Tail --> | Msg 1 Offset |---------->+-----------------+ <-- Tail n + * +-----------------+ | Msg 1 | + * | Msg 2 Offset |--------. | | + * +-----------------+ | | | + * | Msg 3 Offset |------. '->+-----------------+ + * +-----------------+ | | Msg 2 | + * Head --> | Head Offset |--. | | | + * +-----------------+ | | | | + * | | | | | | + * +-----------------+ | | | | + * | | | '--->+-----------------+ + * +-----------------+ | | Msg 3 | + * | | | | | + * +-----------------+ '------->+-----------------+ <-- dataHead + * | | | | + */ + + // Determine the index for the end of the circular ring buffer + // offset list + listEnd = rbOffsetHead; + WRAP_OFFSET(listEnd, 1, rbOffsetEntries); + + // Update the tail, walk newest message to oldest message + rbOffsetTail = rbOffsetHead; + bufferedData = 0; + messageLength = 0; + while ((rbOffsetTail != listEnd) && (bufferedData < use)) + { + // Determine the amount of data in the ring buffer up until + // either the tail or the end of the rbOffsetArray + // + // | | + // | | Valid, still in ring buffer + // | Newest | + // +-----------+ <-- rbOffsetHead + // | | + // | | free space + // | | + // rbOffsetTail --> +-----------+ <-- bufferedData + // | ring | + // | buffer | <-- used + // | data | + // +-----------+ Valid, still in ring buffer + // | | + // + messageLength = rbOffsetArray[rbOffsetTail]; + WRAP_OFFSET(rbOffsetTail, rbOffsetEntries - 1, rbOffsetEntries); + messageLength -= rbOffsetArray[rbOffsetTail]; + if (messageLength < 0) + messageLength += settings.gnssHandlerBufferSize; + bufferedData += messageLength; + } + + // Account for any data in the ring buffer not described by the array // // | | - // | | Valid, still in ring buffer + // +-----------+ + // | Oldest | + // | | + // | ring | + // | buffer | <-- used + // | data | + // +-----------+ Valid, still in ring buffer + // | | + // rbOffsetTail --> +-----------+ <-- bufferedData + // | | // | Newest | // +-----------+ <-- rbOffsetHead // | | - // | | free space + // + if (bufferedData < use) + discardedBytes = use - bufferedData; + + // Writing to the SD card, the network or Bluetooth, a partial + // message may be written leaving the tail pointer mid-message + // + // | | + // rbOffsetTail --> +-----------+ + // | Oldest | // | | - // rbOffsetTail --> +-----------+ <-- bufferedData // | ring | // | buffer | <-- used - // | data | - // +-----------+ Valid, still in ring buffer + // | data | Valid, still in ring buffer + // +-----------+ <-- + // | | + // +-----------+ + // | | + // | Newest | + // +-----------+ <-- rbOffsetHead // | | // - messageLength = rbOffsetArray[rbOffsetTail]; - WRAP_OFFSET(rbOffsetTail, rbOffsetEntries - 1, rbOffsetEntries); - messageLength -= rbOffsetArray[rbOffsetTail]; - if (messageLength < 0) - messageLength += settings.gnssHandlerBufferSize; - bufferedData += messageLength; - } + else if (bufferedData > use) + { + // Remove the remaining portion of the oldest entry in the array + discardedBytes = messageLength + use - bufferedData; + WRAP_OFFSET(rbOffsetTail, 1, rbOffsetEntries); + } - // Account for any data in the ring buffer not described by the array - // - // | | - // +-----------+ - // | Oldest | - // | | - // | ring | - // | buffer | <-- used - // | data | - // +-----------+ Valid, still in ring buffer - // | | - // rbOffsetTail --> +-----------+ <-- bufferedData - // | | - // | Newest | - // +-----------+ <-- rbOffsetHead - // | | - // - discardedBytes = 0; - if (bufferedData < use) - discardedBytes = use - bufferedData; + // rbOffsetTail now points to the beginning of a message in the + // ring buffer + // Determine the amount of data to discard + bytesToDiscard = discardedBytes; + if (bytesToDiscard < bytesToCopy) + bytesToDiscard = bytesToCopy; + if (bytesToDiscard < AMOUNT_OF_RING_BUFFER_DATA_TO_DISCARD) + bytesToDiscard = AMOUNT_OF_RING_BUFFER_DATA_TO_DISCARD; + + // Walk the ring buffer messages from oldest to newest + while ((discardedBytes < bytesToDiscard) && (rbOffsetTail != rbOffsetHead)) + { + // Determine the length of the oldest message + WRAP_OFFSET(rbOffsetTail, 1, rbOffsetEntries); + discardedBytes = rbOffsetArray[rbOffsetTail] - previousTail; + if (discardedBytes < 0) + discardedBytes += settings.gnssHandlerBufferSize; + } - // Writing to the SD card, the network or Bluetooth, a partial - // message may be written leaving the tail pointer mid-message - // - // | | - // rbOffsetTail --> +-----------+ - // | Oldest | - // | | - // | ring | - // | buffer | <-- used - // | data | Valid, still in ring buffer - // +-----------+ <-- - // | | - // +-----------+ - // | | - // | Newest | - // +-----------+ <-- rbOffsetHead - // | | - // - else if (bufferedData > use) - { - // Remove the remaining portion of the oldest entry in the array - discardedBytes = messageLength + use - bufferedData; - WRAP_OFFSET(rbOffsetTail, 1, rbOffsetEntries); + // Discard the oldest data from the ring buffer + // Printing the slow consumer is not that useful as any consumer will be + // considered 'slow' if its data wraps over the end of the buffer and + // needs a second write to clear... + if (!inMainMenu) + { + if (consumer) + systemPrintf("Ring buffer full: discarding %d bytes, %s could be slow\r\n", discardedBytes, consumer); + else + systemPrintf("Ring buffer full: discarding %d bytes\r\n", discardedBytes); + Serial.flush(); // TODO - delete me! + } + + // Update the tails. This needs semaphore protection + updateRingBufferTails(previousTail, rbOffsetArray[rbOffsetTail]); } - // rbOffsetTail now points to the beginning of a message in the - // ring buffer - // Determine the amount of data to discard - bytesToDiscard = discardedBytes; - if (bytesToDiscard < bytesToCopy) - bytesToDiscard = bytesToCopy; - if (bytesToDiscard < AMOUNT_OF_RING_BUFFER_DATA_TO_DISCARD) - bytesToDiscard = AMOUNT_OF_RING_BUFFER_DATA_TO_DISCARD; - - // Walk the ring buffer messages from oldest to newest - while ((discardedBytes < bytesToDiscard) && (rbOffsetTail != rbOffsetHead)) + if (bytesToCopy > (space + discardedBytes - 1)) // Sanity check { - // Determine the length of the oldest message - WRAP_OFFSET(rbOffsetTail, 1, rbOffsetEntries); - discardedBytes = rbOffsetArray[rbOffsetTail] - previousTail; - if (discardedBytes < 0) - discardedBytes += settings.gnssHandlerBufferSize; + systemPrintf("Ring buffer update error %s: bytesToCopy (%d) is > space (%d) + discardedBytes (%d) - 1\r\n", + getTimeStamp(), bytesToCopy, space, discardedBytes); + Serial.flush(); // Flush Serial - the code is about to go bang...! + } + + // Add another message to the ring buffer + // Account for this message + // Diagnostic prints are provided by settings.enablePrintSDBuffers and the handleGnssDataTask + // The semaphore prevents badness here. Previously availableHandlerSpace may have been updated + // by handleGnssDataTask + availableHandlerSpace = availableHandlerSpace + discardedBytes - bytesToCopy; + + // Copy dataHead so we can update with a single write - redundant with the semaphore + RING_BUFFER_OFFSET newDataHead = dataHead; + + // Fill the buffer to the end and then start at the beginning + if ((newDataHead + bytesToCopy) > settings.gnssHandlerBufferSize) + bytesToCopy = settings.gnssHandlerBufferSize - newDataHead; + + // Display the dataHead offset + if (settings.enablePrintRingBufferOffsets && (!inMainMenu)) + systemPrintf("DH: %4d --> ", newDataHead); + + // Copy the data into the ring buffer + memcpy(&ringBuffer[newDataHead], parse->buffer, bytesToCopy); + newDataHead = newDataHead + bytesToCopy; + if (newDataHead >= settings.gnssHandlerBufferSize) + newDataHead = newDataHead - settings.gnssHandlerBufferSize; + + // Determine the remaining bytes + remainingBytes = parse->length - bytesToCopy; + if (remainingBytes) + { + // Copy the remaining bytes into the beginning of the ring buffer + memcpy(ringBuffer, &parse->buffer[bytesToCopy], remainingBytes); + newDataHead = newDataHead + remainingBytes; + if (newDataHead >= settings.gnssHandlerBufferSize) + newDataHead = newDataHead - settings.gnssHandlerBufferSize; } - // Discard the oldest data from the ring buffer - if (consumer) - systemPrintf("Ring buffer full: discarding %d bytes, %s is slow\r\n", discardedBytes, consumer); - else - systemPrintf("Ring buffer full: discarding %d bytes\r\n", discardedBytes); - updateRingBufferTails(previousTail, rbOffsetArray[rbOffsetTail]); - availableHandlerSpace = availableHandlerSpace + discardedBytes; - } - - // Add another message to the ring buffer - // Account for this message - availableHandlerSpace = availableHandlerSpace - bytesToCopy; - - // Fill the buffer to the end and then start at the beginning - if ((dataHead + bytesToCopy) > settings.gnssHandlerBufferSize) - bytesToCopy = settings.gnssHandlerBufferSize - dataHead; + // Add the head offset to the offset array + WRAP_OFFSET(rbOffsetHead, 1, rbOffsetEntries); + rbOffsetArray[rbOffsetHead] = newDataHead; - // Display the dataHead offset - if (settings.enablePrintRingBufferOffsets && (!inMainMenu)) - systemPrintf("DH: %4d --> ", dataHead); + // Display the dataHead offset + if (settings.enablePrintRingBufferOffsets && (!inMainMenu)) + systemPrintf("%4d @ %s\r\n", newDataHead, getTimeStamp()); - // Copy the data into the ring buffer - memcpy(&ringBuffer[dataHead], parse->buffer, bytesToCopy); - dataHead = dataHead + bytesToCopy; - if (dataHead >= settings.gnssHandlerBufferSize) - dataHead = dataHead - settings.gnssHandlerBufferSize; + // Update dataHead in a single write - redundant with the semaphore + // handleGnssDataTask will use it as soon as it updates + dataHead = newDataHead; - // Determine the remaining bytes - remainingBytes = parse->length - bytesToCopy; - if (remainingBytes) + // handleGnssDataTask will be chomping at the bit. Let it fly! + xSemaphoreGive(ringBufferSemaphore); + } + else { - // Copy the remaining bytes into the beginning of the ring buffer - memcpy(ringBuffer, &parse->buffer[bytesToCopy], remainingBytes); - dataHead = dataHead + remainingBytes; - if (dataHead >= settings.gnssHandlerBufferSize) - dataHead = dataHead - settings.gnssHandlerBufferSize; + systemPrintf("processUart1Message could not get ringBuffer semaphore - held by %s\r\n", ringBufferSemaphoreHolder); } - - // Add the head offset to the offset array - WRAP_OFFSET(rbOffsetHead, 1, rbOffsetEntries); - rbOffsetArray[rbOffsetHead] = dataHead; - - // Display the dataHead offset - if (settings.enablePrintRingBufferOffsets && (!inMainMenu)) - systemPrintf("%4d\r\n", dataHead); } // Remove previous messages from the ring buffer @@ -1002,10 +1058,11 @@ void updateRingBufferTails(RING_BUFFER_OFFSET previousTail, RING_BUFFER_OFFSET n { // Trim any long or medium tails discardRingBufferBytes(&btRingBufferTail, previousTail, newTail); - discardRingBufferBytes(&sdRingBufferTail, previousTail, newTail); tcpClientDiscardBytes(previousTail, newTail); tcpServerDiscardBytes(previousTail, newTail); udpServerDiscardBytes(previousTail, newTail); + discardRingBufferBytes(&sdRingBufferTail, previousTail, newTail); + discardRingBufferBytes(&usbRingBufferTail, previousTail, newTail); } // Remove previous messages from the ring buffer @@ -1056,7 +1113,7 @@ void handleGnssDataTask(void *e) uint32_t deltaMillis; int32_t freeSpace; static uint32_t maxMillis[RBC_MAX]; - uint32_t startMillis; + unsigned long startMillis; int32_t usedSpace; // Start notification @@ -1070,6 +1127,7 @@ void handleGnssDataTask(void *e) tcpServerZeroTail(); udpServerZeroTail(); sdRingBufferTail = 0; + usbRingBufferTail = 0; // Run task until a request is raised task.handleGnssDataTaskStopRequest = false; @@ -1084,337 +1142,445 @@ void handleGnssDataTask(void *e) usedSpace = 0; - //---------------------------------------------------------------------- - // Send data over Bluetooth - //---------------------------------------------------------------------- - - startMillis = millis(); + // Use a semaphore to prevent handleGnssDataTask from gatecrashing + if (ringBufferSemaphore == NULL) + ringBufferSemaphore = xSemaphoreCreateMutex(); // Create the mutex + + // Take the semaphore. Short wait. processUart1Message shouldn't block for long + if (xSemaphoreTake(ringBufferSemaphore, ringBuffer_shortWait_ms) == pdPASS) + { + ringBufferSemaphoreHolder = "handleGnssDataTask"; - // Determine BT connection state - bool connected = (bluetoothGetState() == BT_CONNECTED); + //---------------------------------------------------------------------- + // Send data over Bluetooth + //---------------------------------------------------------------------- - if (!connected) - // Discard the data - btRingBufferTail = dataHead; - else - { - // Determine the amount of Bluetooth data in the buffer - bytesToSend = dataHead - btRingBufferTail; - if (bytesToSend < 0) - bytesToSend += settings.gnssHandlerBufferSize; - if (bytesToSend > 0) - { - // Reduce bytes to send if we have more to send then the end of - // the buffer, we'll wrap next loop - if ((btRingBufferTail + bytesToSend) > settings.gnssHandlerBufferSize) - bytesToSend = settings.gnssHandlerBufferSize - btRingBufferTail; + startMillis = millis(); - // If we are in the config menu, suppress data flowing from GNSS to cell phone - if (btPrintEcho == false) - { - // Push new data over Bluetooth - bytesToSend = bluetoothWrite(&ringBuffer[btRingBufferTail], bytesToSend); - } + // Determine BT connection state + bool connected = (bluetoothGetState() == BT_CONNECTED); - // Account for the data that was sent + if (!connected) + // Discard the data + btRingBufferTail = dataHead; + else + { + // Determine the amount of Bluetooth data in the buffer + bytesToSend = dataHead - btRingBufferTail; + if (bytesToSend < 0) + bytesToSend += settings.gnssHandlerBufferSize; if (bytesToSend > 0) { - // If we are in base mode, assume part of the outgoing data is RTCM - if (inBaseMode() == true) - bluetoothOutgoingRTCM = true; - - // Account for the sent or dropped data - btRingBufferTail += bytesToSend; - if (btRingBufferTail >= settings.gnssHandlerBufferSize) - btRingBufferTail -= settings.gnssHandlerBufferSize; - - // Remember the maximum transfer time - deltaMillis = millis() - startMillis; - if (maxMillis[RBC_BLUETOOTH] < deltaMillis) - maxMillis[RBC_BLUETOOTH] = deltaMillis; - - // Display the data movement - if (PERIODIC_DISPLAY(PD_BLUETOOTH_DATA_TX)) + // Reduce bytes to send if we have more to send then the end of + // the buffer, we'll wrap next loop + if ((btRingBufferTail + bytesToSend) > settings.gnssHandlerBufferSize) + bytesToSend = settings.gnssHandlerBufferSize - btRingBufferTail; + + // If we are in the config menu, suppress data flowing from GNSS to cell phone + if (btPrintEcho == false) { - PERIODIC_CLEAR(PD_BLUETOOTH_DATA_TX); - systemPrintf("Bluetooth: %d bytes written\r\n", bytesToSend); + // Push new data over Bluetooth + bytesToSend = bluetoothWrite(&ringBuffer[btRingBufferTail], bytesToSend); + } + + // Account for the data that was sent + if (bytesToSend > 0) + { + // If we are in base mode, assume part of the outgoing data is RTCM + if (inBaseMode() == true) + bluetoothOutgoingRTCM = true; + + // Account for the sent or dropped data + btRingBufferTail += bytesToSend; + if (btRingBufferTail >= settings.gnssHandlerBufferSize) + btRingBufferTail -= settings.gnssHandlerBufferSize; + + // Remember the maximum transfer time + deltaMillis = millis() - startMillis; + if (maxMillis[RBC_BLUETOOTH] < deltaMillis) + maxMillis[RBC_BLUETOOTH] = deltaMillis; + + // Display the data movement + if (PERIODIC_DISPLAY(PD_BLUETOOTH_DATA_TX)) + { + PERIODIC_CLEAR(PD_BLUETOOTH_DATA_TX); + systemPrintf("Bluetooth: %d bytes written\r\n", bytesToSend); + } + } + else + log_w("BT failed to send"); + + // Determine the amount of data that remains in the buffer + bytesToSend = dataHead - btRingBufferTail; + if (bytesToSend < 0) + bytesToSend += settings.gnssHandlerBufferSize; + if (usedSpace < bytesToSend) + { + usedSpace = bytesToSend; + slowConsumer = "Bluetooth"; } } - else - log_w("BT failed to send"); + } - // Determine the amount of data that remains in the buffer - bytesToSend = dataHead - btRingBufferTail; + //---------------------------------------------------------------------- + // Send data over USB serial + //---------------------------------------------------------------------- + + startMillis = millis(); + + // Determine USB serial connection state + if (!forwardGnssDataToUsbSerial) + // Discard the data + usbRingBufferTail = dataHead; + else + { + // Determine the amount of USB serial data in the buffer + bytesToSend = dataHead - usbRingBufferTail; if (bytesToSend < 0) bytesToSend += settings.gnssHandlerBufferSize; - if (usedSpace < bytesToSend) + if (bytesToSend > 0) { - usedSpace = bytesToSend; - slowConsumer = "Bluetooth"; + // Reduce bytes to send if we have more to send then the end of + // the buffer, we'll wrap next loop + if ((usbRingBufferTail + bytesToSend) > settings.gnssHandlerBufferSize) + bytesToSend = settings.gnssHandlerBufferSize - usbRingBufferTail; + + // Send data over USB serial to the PC + bytesToSend = systemWriteGnssDataToUsbSerial(&ringBuffer[usbRingBufferTail], bytesToSend); + + // Account for the data that was sent + if (bytesToSend > 0) + { + // Account for the sent or dropped data + usbRingBufferTail += bytesToSend; + if (usbRingBufferTail >= settings.gnssHandlerBufferSize) + usbRingBufferTail -= settings.gnssHandlerBufferSize; + + // Remember the maximum transfer time + deltaMillis = millis() - startMillis; + if (maxMillis[RBC_USB_SERIAL] < deltaMillis) + maxMillis[RBC_USB_SERIAL] = deltaMillis; + } + + // Determine the amount of data that remains in the buffer + bytesToSend = dataHead - usbRingBufferTail; + if (bytesToSend < 0) + bytesToSend += settings.gnssHandlerBufferSize; + if (usedSpace < bytesToSend) + { + usedSpace = bytesToSend; + slowConsumer = "USB Serial"; + } } } - } - //---------------------------------------------------------------------- - // Send data over USB serial - //---------------------------------------------------------------------- + //---------------------------------------------------------------------- + // Send data to the network clients + //---------------------------------------------------------------------- - startMillis = millis(); + startMillis = millis(); - // Determine USB serial connection state - if (!forwardGnssDataToUsbSerial) - // Discard the data - usbRingBufferTail = dataHead; - else - { - // Determine the amount of USB serial data in the buffer - bytesToSend = dataHead - usbRingBufferTail; - if (bytesToSend < 0) - bytesToSend += settings.gnssHandlerBufferSize; - if (bytesToSend > 0) + // Update space available for use in UART task + bytesToSend = tcpClientSendData(dataHead); + if (usedSpace < bytesToSend) { - // Reduce bytes to send if we have more to send then the end of - // the buffer, we'll wrap next loop - if ((usbRingBufferTail + bytesToSend) > settings.gnssHandlerBufferSize) - bytesToSend = settings.gnssHandlerBufferSize - usbRingBufferTail; + usedSpace = bytesToSend; + slowConsumer = "TCP client"; + } - // Send data over USB serial to the PC - bytesToSend = systemWriteGnssDataToUsbSerial(&ringBuffer[usbRingBufferTail], bytesToSend); + // Remember the maximum transfer time + deltaMillis = millis() - startMillis; + if (maxMillis[RBC_TCP_CLIENT] < deltaMillis) + maxMillis[RBC_TCP_CLIENT] = deltaMillis; - // Account for the data that was sent - if (bytesToSend > 0) - { - // Account for the sent or dropped data - usbRingBufferTail += bytesToSend; - if (usbRingBufferTail >= settings.gnssHandlerBufferSize) - usbRingBufferTail -= settings.gnssHandlerBufferSize; - - // Remember the maximum transfer time - deltaMillis = millis() - startMillis; - if (maxMillis[RBC_USB_SERIAL] < deltaMillis) - maxMillis[RBC_USB_SERIAL] = deltaMillis; - } + startMillis = millis(); - // Determine the amount of data that remains in the buffer - bytesToSend = dataHead - usbRingBufferTail; + // Update space available for use in UART task + bytesToSend = tcpServerSendData(dataHead); + if (usedSpace < bytesToSend) + { + usedSpace = bytesToSend; + slowConsumer = "TCP server"; + } + + // Remember the maximum transfer time + deltaMillis = millis() - startMillis; + if (maxMillis[RBC_TCP_SERVER] < deltaMillis) + maxMillis[RBC_TCP_SERVER] = deltaMillis; + + startMillis = millis(); + + // Update space available for use in UART task + bytesToSend = udpServerSendData(dataHead); + if (usedSpace < bytesToSend) + { + usedSpace = bytesToSend; + slowConsumer = "UDP server"; + } + + // Remember the maximum transfer time + deltaMillis = millis() - startMillis; + if (maxMillis[RBC_UDP_SERVER] < deltaMillis) + maxMillis[RBC_UDP_SERVER] = deltaMillis; + + //---------------------------------------------------------------------- + // Log data to the SD card + //---------------------------------------------------------------------- + + // Determine if the SD card is enabled for logging + connected = online.logging && (!logTimeExceeded()); + + // Block logging during Web Config to avoid SD collisions + // See issue: https://github.com/sparkfun/SparkFun_RTK_Everywhere_Firmware/issues/693 + if(webServerIsRunning() == true) + connected = false; + + // If user wants to log, record to SD + if (!connected) + // Discard the data + sdRingBufferTail = dataHead; + else + { + // Determine the amount of microSD card logging data in the buffer + bytesToSend = dataHead - sdRingBufferTail; if (bytesToSend < 0) bytesToSend += settings.gnssHandlerBufferSize; - if (usedSpace < bytesToSend) + if (bytesToSend > 0) { - usedSpace = bytesToSend; - slowConsumer = "USB Serial"; - } - } - } + // Attempt to gain access to the SD card, avoids collisions with file + // writing from other functions like recordSystemSettingsToFile() + if (xSemaphoreTake(sdCardSemaphore, loggingSemaphoreWait_ms) == pdPASS) + { + markSemaphore(FUNCTION_WRITESD); - //---------------------------------------------------------------------- - // Send data to the network clients - //---------------------------------------------------------------------- + do // Do the SD write in a do loop so we can break out if needed + { + if (settings.enablePrintSDBuffers && (!inMainMenu)) + { + int bufferAvailable = serialGNSS->available(); - startMillis = millis(); + int availableUARTSpace = settings.uartReceiveBufferSize - bufferAvailable; - // Update space available for use in UART task - bytesToSend = tcpClientSendData(dataHead); - if (usedSpace < bytesToSend) - { - usedSpace = bytesToSend; - slowConsumer = "TCP client"; - } + systemPrintf("SD Incoming Serial @ %s: %04d\tToRead: %04d\tMovedToBuffer: %04d\tavailableUARTSpace: " + "%04d\tavailableHandlerSpace: %04d\tToRecord: %04d\tRecorded: %04d\tBO: %d\r\n", + getTimeStamp(), bufferAvailable, 0, 0, availableUARTSpace, availableHandlerSpace, + bytesToSend, 0, bufferOverruns); + } - // Remember the maximum transfer time - deltaMillis = millis() - startMillis; - if (maxMillis[RBC_TCP_CLIENT] < deltaMillis) - maxMillis[RBC_TCP_CLIENT] = deltaMillis; + // For the SD card, we need to write everything we've got + // to prevent the ARP Write and Events from gatecrashing... + + int32_t sendTheseBytes = bytesToSend; - startMillis = millis(); + // Reduce bytes to record if we have more then the end of the buffer + if ((sdRingBufferTail + sendTheseBytes) > settings.gnssHandlerBufferSize) + sendTheseBytes = settings.gnssHandlerBufferSize - sdRingBufferTail; - // Update space available for use in UART task - bytesToSend = tcpServerSendData(dataHead); - if (usedSpace < bytesToSend) - { - usedSpace = bytesToSend; - slowConsumer = "TCP server"; - } + startMillis = millis(); - // Remember the maximum transfer time - deltaMillis = millis() - startMillis; - if (maxMillis[RBC_TCP_SERVER] < deltaMillis) - maxMillis[RBC_TCP_SERVER] = deltaMillis; + // Write the data to the file + int32_t bytesSent = logFile->write(&ringBuffer[sdRingBufferTail], sendTheseBytes); - startMillis = millis(); + // Account for the sent data or dropped + sdRingBufferTail += bytesSent; + if (sdRingBufferTail >= settings.gnssHandlerBufferSize) + sdRingBufferTail -= settings.gnssHandlerBufferSize; - // Update space available for use in UART task - bytesToSend = udpServerSendData(dataHead); - if (usedSpace < bytesToSend) - { - usedSpace = bytesToSend; - slowConsumer = "UDP server"; - } + if (bytesSent != sendTheseBytes) + { + systemPrintf("SD write mismatch (1) @ %s: wrote %d bytes of %d\r\n", + getTimeStamp(), bytesSent, sendTheseBytes); + break; // Exit the do loop + } - // Remember the maximum transfer time - deltaMillis = millis() - startMillis; - if (maxMillis[RBC_UDP_SERVER] < deltaMillis) - maxMillis[RBC_UDP_SERVER] = deltaMillis; + // If we have more data to write - and the first write was successful + if (bytesToSend > sendTheseBytes) + { + sendTheseBytes = bytesToSend - sendTheseBytes; - //---------------------------------------------------------------------- - // Log data to the SD card - //---------------------------------------------------------------------- + bytesSent = logFile->write(&ringBuffer[sdRingBufferTail], sendTheseBytes); - // Determine if the SD card is enabled for logging - connected = online.logging && ((systemTime_minutes - startLogTime_minutes) < settings.maxLogTime_minutes); + // Account for the sent data or dropped + sdRingBufferTail += bytesSent; + if (sdRingBufferTail >= settings.gnssHandlerBufferSize) // Should be redundant + sdRingBufferTail -= settings.gnssHandlerBufferSize; - // Block logging during Web Config to avoid SD collisions - // See issue: https://github.com/sparkfun/SparkFun_RTK_Everywhere_Firmware/issues/693 - if(webServerIsRunning() == true) - connected = false; + if (bytesSent != sendTheseBytes) + { + systemPrintf("SD write mismatch (2) @ %s: wrote %d bytes of %d\r\n", + getTimeStamp(), bytesSent, sendTheseBytes); + break; // Exit the do loop + } + } - // If user wants to log, record to SD - if (!connected) - // Discard the data - sdRingBufferTail = dataHead; - else - { - // Determine the amount of microSD card logging data in the buffer - bytesToSend = dataHead - sdRingBufferTail; - if (bytesToSend < 0) - bytesToSend += settings.gnssHandlerBufferSize; - if (bytesToSend > 0) - { - // Attempt to gain access to the SD card, avoids collisions with file - // writing from other functions like recordSystemSettingsToFile() - if (xSemaphoreTake(sdCardSemaphore, loggingSemaphoreWait_ms) == pdPASS) - { - markSemaphore(FUNCTION_WRITESD); + if (PERIODIC_DISPLAY(PD_SD_LOG_WRITE) && (bytesSent > 0)) + { + PERIODIC_CLEAR(PD_SD_LOG_WRITE); + systemPrintf("SD %d bytes written to log file\r\n", bytesToSend); + } - // Reduce bytes to record if we have more then the end of the buffer - if ((sdRingBufferTail + bytesToSend) > settings.gnssHandlerBufferSize) - bytesToSend = settings.gnssHandlerBufferSize - sdRingBufferTail; + sdFreeSpace -= bytesToSend; // Update remaining space on SD - if (settings.enablePrintSDBuffers && (!inMainMenu)) - { - int bufferAvailable = serialGNSS->available(); + // Record any pending trigger events + if (newEventToRecord == true) + { + newEventToRecord = false; - int availableUARTSpace = settings.uartReceiveBufferSize - bufferAvailable; + if (settings.enablePrintLogFileStatus) + systemPrintln("Log file: recording event"); - systemPrintf("SD Incoming Serial: %04d\tToRead: %04d\tMovedToBuffer: %04d\tavailableUARTSpace: " - "%04d\tavailableHandlerSpace: %04d\tToRecord: %04d\tRecorded: %04d\tBO: %d\r\n", - bufferAvailable, 0, 0, availableUARTSpace, availableHandlerSpace, bytesToSend, 0, - bufferOverruns); - } + // Record trigger count with Time Of Week of rising edge (ms), Millisecond fraction of Time Of Week of + // rising edge (ns), and accuracy estimate (ns) + char eventData[82]; // Max NMEA sentence length is 82 + snprintf(eventData, sizeof(eventData), "%d,%d,%d,%d", triggerCount, triggerTowMsR, triggerTowSubMsR, + triggerAccEst); - // Write the data to the file - long startTime = millis(); - startMillis = millis(); + char nmeaMessage[82]; // Max NMEA sentence length is 82 + createNMEASentence(CUSTOM_NMEA_TYPE_EVENT, nmeaMessage, sizeof(nmeaMessage), + eventData); // textID, buffer, sizeOfBuffer, text - bytesToSend = logFile->write(&ringBuffer[sdRingBufferTail], bytesToSend); - if (PERIODIC_DISPLAY(PD_SD_LOG_WRITE) && (bytesToSend > 0)) - { - PERIODIC_CLEAR(PD_SD_LOG_WRITE); - systemPrintf("SD %d bytes written to log file\r\n", bytesToSend); - } + logFile->write(nmeaMessage, strlen(nmeaMessage)); + const char *crlf = "\r\n"; + logFile->write(crlf, 2); - logFileSize = logFile->fileSize(); // Update file size + sdFreeSpace -= strlen(nmeaMessage) + 2; // Update remaining space on SD + } - sdFreeSpace -= bytesToSend; // Update remaining space on SD + // Record the Antenna Reference Position - if available + if (newARPAvailable == true && settings.enableARPLogging && + ((millis() - lastARPLog) > (settings.ARPLoggingInterval_s * 1000))) + { + lastARPLog = millis(); + newARPAvailable = false; // Clear flag. It doesn't matter if the ARP cannot be logged + + double x = ARPECEFX; + x /= 10000.0; // Convert to m + double y = ARPECEFY; + y /= 10000.0; // Convert to m + double z = ARPECEFZ; + z /= 10000.0; // Convert to m + double h = ARPECEFH; + h /= 10000.0; // Convert to m + char ARPData[82]; // Max NMEA sentence length is 82 + snprintf(ARPData, sizeof(ARPData), "%.4f,%.4f,%.4f,%.4f", x, y, z, h); + + if (settings.enablePrintLogFileStatus) + systemPrintf("Log file: recording Antenna Reference Position %s\r\n", ARPData); + + char nmeaMessage[82]; // Max NMEA sentence length is 82 + createNMEASentence(CUSTOM_NMEA_TYPE_ARP_ECEF_XYZH, nmeaMessage, sizeof(nmeaMessage), + ARPData); // textID, buffer, sizeOfBuffer, text + + logFile->write(nmeaMessage, strlen(nmeaMessage)); + const char *crlf = "\r\n"; + logFile->write(crlf, 2); + + sdFreeSpace -= strlen(nmeaMessage) + 2; // Update remaining space on SD + } - // Force file sync every 60s - if (millis() - lastUBXLogSyncTime > 60000) - { - baseStatusLedBlink(); // Blink LED to indicate logging activity + logFileSize = logFile->fileSize(); // Update file size - logFile->sync(); - sdUpdateFileAccessTimestamp(logFile); // Update the file access time & date + // Force file sync every 60s + if (millis() - lastUBXLogSyncTime > 60000) + { + baseStatusLedBlink(); // Blink LED to indicate logging activity - baseStatusLedBlink(); // Blink LED to indicate logging activity + logFile->sync(); + sdUpdateFileAccessTimestamp(logFile); // Update the file access time & date - lastUBXLogSyncTime = millis(); - } + baseStatusLedBlink(); // Blink LED to indicate logging activity - // Remember the maximum transfer time - deltaMillis = millis() - startMillis; - if (maxMillis[RBC_SD_CARD] < deltaMillis) - maxMillis[RBC_SD_CARD] = deltaMillis; - long endTime = millis(); + lastUBXLogSyncTime = millis(); + } - if (settings.enablePrintBufferOverrun) - { - if (endTime - startTime > 150) - systemPrintf("Long Write! Time: %ld ms / Location: %ld / Recorded %d bytes / " - "spaceRemaining %d bytes\r\n", - endTime - startTime, logFileSize, bytesToSend, combinedSpaceRemaining); - } + // Remember the maximum transfer time + deltaMillis = millis() - startMillis; + if (maxMillis[RBC_SD_CARD] < deltaMillis) + maxMillis[RBC_SD_CARD] = deltaMillis; - xSemaphoreGive(sdCardSemaphore); + if (settings.enablePrintBufferOverrun) + { + if (deltaMillis > 150) + systemPrintf("Long Write! Time: %ld ms / Location: %ld / Recorded %d bytes / " + "spaceRemaining %d bytes\r\n", + deltaMillis, logFileSize, bytesToSend, combinedSpaceRemaining); + } + } while(0); - // Account for the sent data or dropped - if (bytesToSend > 0) + xSemaphoreGive(sdCardSemaphore); + } // End sdCardSemaphore + else { - sdRingBufferTail += bytesToSend; - if (sdRingBufferTail >= settings.gnssHandlerBufferSize) - sdRingBufferTail -= settings.gnssHandlerBufferSize; - } - } // End sdCardSemaphore - else - { - char semaphoreHolder[50]; - getSemaphoreFunction(semaphoreHolder); - log_w("sdCardSemaphore failed to yield for SD write, held by %s, Tasks.ino line %d", - semaphoreHolder, __LINE__); - - delay(1); // Needed to prevent WDT resets during long Record Settings locks - taskYIELD(); - } + char semaphoreHolder[50]; + getSemaphoreFunction(semaphoreHolder); + log_w("sdCardSemaphore failed to yield for SD write, held by %s, Tasks.ino line %d", + semaphoreHolder, __LINE__); - // Update space available for use in UART task - bytesToSend = dataHead - sdRingBufferTail; - if (bytesToSend < 0) - bytesToSend += settings.gnssHandlerBufferSize; - if (usedSpace < bytesToSend) - { - usedSpace = bytesToSend; - slowConsumer = "SD card"; - } - } // bytesToSend - } // End connected + feedWdt(); + taskYIELD(); + } - //---------------------------------------------------------------------- - // Update the available space in the ring buffer - //---------------------------------------------------------------------- + // Update space available for use in UART task + bytesToSend = dataHead - sdRingBufferTail; + if (bytesToSend < 0) + bytesToSend += settings.gnssHandlerBufferSize; + if (usedSpace < bytesToSend) + { + usedSpace = bytesToSend; + slowConsumer = "SD card"; + } + } // bytesToSend + } // End connected - freeSpace = settings.gnssHandlerBufferSize - usedSpace; + //---------------------------------------------------------------------- + // Update the available space in the ring buffer + //---------------------------------------------------------------------- - // Don't fill the last byte to prevent buffer overflow - if (freeSpace) - freeSpace -= 1; - availableHandlerSpace = freeSpace; + freeSpace = settings.gnssHandlerBufferSize - usedSpace; - //---------------------------------------------------------------------- - // Display the millisecond values for the different ring buffer consumers - //---------------------------------------------------------------------- + // Don't fill the last byte to prevent buffer overflow + if (freeSpace) + freeSpace -= 1; + availableHandlerSpace = freeSpace; - if (PERIODIC_DISPLAY(PD_RING_BUFFER_MILLIS)) - { - int milliseconds; - int seconds; + //---------------------------------------------------------------------- + // Display the millisecond values for the different ring buffer consumers + //---------------------------------------------------------------------- - PERIODIC_CLEAR(PD_RING_BUFFER_MILLIS); - for (int index = 0; index < RBC_MAX; index++) + if (PERIODIC_DISPLAY(PD_RING_BUFFER_MILLIS)) { - milliseconds = maxMillis[index]; - if (milliseconds > 1) + int milliseconds; + int seconds; + + PERIODIC_CLEAR(PD_RING_BUFFER_MILLIS); + for (int index = 0; index < RBC_MAX; index++) { - seconds = milliseconds / MILLISECONDS_IN_A_SECOND; - milliseconds %= MILLISECONDS_IN_A_SECOND; - systemPrintf("%s: %d:%03d Sec\r\n", ringBufferConsumer[index], seconds, milliseconds); + milliseconds = maxMillis[index]; + if (milliseconds > 1) + { + seconds = milliseconds / MILLISECONDS_IN_A_SECOND; + milliseconds %= MILLISECONDS_IN_A_SECOND; + systemPrintf("%s: %d:%03d Sec\r\n", ringBufferConsumer[index], seconds, milliseconds); + } } } + + //---------------------------------------------------------------------- + // processUart1Message will be chomping at the bit. Let it fly! + //---------------------------------------------------------------------- + + xSemaphoreGive(ringBufferSemaphore); + } + else + { + systemPrintf("handleGnssDataTask could not get ringBuffer semaphore - held by %s\r\n", ringBufferSemaphoreHolder); } //---------------------------------------------------------------------- // Let other tasks run, prevent watch dog timer (WDT) resets //---------------------------------------------------------------------- - delay(1); + feedWdt(); taskYIELD(); } @@ -2123,7 +2289,7 @@ void sdSizeCheckTask(void *e) } } - delay(1); + feedWdt(); taskYIELD(); // Let other tasks run } @@ -2200,3 +2366,45 @@ void bluetoothCommandTask(void *pvParameters) task.bluetoothCommandTaskRunning = false; vTaskDelete(NULL); } + +void beginRtcmParse() +{ + // Begin the RTCM parser - which will extract the base location from RTCM1005 / 1006 + rtcmParse = sempBeginParser(rtcmParserTable, rtcmParserCount, rtcmParserNames, rtcmParserNameCount, + 0, // Scratchpad bytes + 1050, // Buffer length + processRTCMMessage, // eom Call Back + "rtcmParse"); // Parser Name + if (!rtcmParse) + reportFatalError("Failed to initialize the RTCM parser"); + + if (settings.debugNtripClientRtcm) + { + sempEnableDebugOutput(rtcmParse); + sempPrintParserConfiguration(rtcmParse); + } +} + +// Check and record the base location in RTCM1005/1006 +void processRTCMMessage(SEMP_PARSE_STATE *parse, uint16_t type) +{ + SEMP_SCRATCH_PAD *scratchPad = (SEMP_SCRATCH_PAD *)parse->scratchPad; + + if (sempRtcmGetMessageNumber(parse) == 1005) + { + ARPECEFX = sempRtcmGetSignedBits(parse, 34, 38); + ARPECEFY = sempRtcmGetSignedBits(parse, 74, 38); + ARPECEFZ = sempRtcmGetSignedBits(parse, 114, 38); + ARPECEFH = 0; + newARPAvailable = true; + } + + if (sempRtcmGetMessageNumber(parse) == 1006) + { + ARPECEFX = sempRtcmGetSignedBits(parse, 34, 38); + ARPECEFY = sempRtcmGetSignedBits(parse, 74, 38); + ARPECEFZ = sempRtcmGetSignedBits(parse, 114, 38); + ARPECEFH = sempRtcmGetUnsignedBits(parse, 152, 16); + newARPAvailable = true; + } +} \ No newline at end of file diff --git a/Firmware/RTK_Everywhere/TcpServer.ino b/Firmware/RTK_Everywhere/TcpServer.ino index 170f86979..7f4385570 100644 --- a/Firmware/RTK_Everywhere/TcpServer.ino +++ b/Firmware/RTK_Everywhere/TcpServer.ino @@ -139,26 +139,42 @@ int32_t tcpServerClientSendData(int index, uint8_t *data, uint16_t length) { if (tcpServerClient[index]) { - length = tcpServerClient[index]->write(data, length); - if (length > 0) + // Use a semaphore to prevent tcpServerUpdate from gatecrashing + if (tcpServerSemaphore == NULL) + tcpServerSemaphore = xSemaphoreCreateMutex(); // Create the mutex + + // Take the semaphore + if (xSemaphoreTake(tcpServerSemaphore, 50 / portTICK_PERIOD_MS) == pdPASS) { - // Update the data sent flag when data successfully sent + tcpServerSemaphoreHolder = "tcpServerClientSendData"; + + length = tcpServerClient[index]->write(data, length); if (length > 0) - tcpServerClientDataSent = tcpServerClientDataSent | (1 << index); - if ((settings.debugTcpServer || PERIODIC_DISPLAY(PD_TCP_SERVER_CLIENT_DATA)) && (!inMainMenu)) { - PERIODIC_CLEAR(PD_TCP_SERVER_CLIENT_DATA); - systemPrintf("TCP server wrote %d bytes to %s\r\n", length, - tcpServerClientIpAddress[index].toString().c_str()); + // Update the data sent flag when data successfully sent + if (length > 0) + tcpServerClientDataSent = tcpServerClientDataSent | (1 << index); + if ((settings.debugTcpServer || PERIODIC_DISPLAY(PD_TCP_SERVER_CLIENT_DATA)) && (!inMainMenu)) + { + PERIODIC_CLEAR(PD_TCP_SERVER_CLIENT_DATA); + systemPrintf("TCP server wrote %d bytes to %s\r\n", length, + tcpServerClientIpAddress[index].toString().c_str()); + } } - } - // Failed to write the data + // Failed to write the data + else + { + // Done with this client connection + tcpServerStopClient(index); + length = 0; + } + + xSemaphoreGive(tcpServerSemaphore); + } else { - // Done with this client connection - tcpServerStopClient(index); - length = 0; + systemPrintf("tcpServerClientSendData could not get semaphore - held by %s\r\n", tcpServerSemaphoreHolder); } } return length; @@ -553,46 +569,62 @@ void tcpServerUpdate() // and respond accordingly if (settings.enableNtripCaster || settings.baseCasterOverride) { - // Read response from client - char response[512]; - int spot = 0; - while (tcpServerClient[index]->available()) - { - response[spot++] = tcpServerClient[index]->read(); - if (spot == sizeof(response)) - spot = 0; // Wrap - } - response[spot] = '\0'; // Terminate string - - if (strnstr(response, "GET / ", sizeof(response)) != NULL) // No mount point in header - { - if (settings.debugTcpServer) - systemPrintln("Mount point table requested."); - - // Respond with a single mountpoint - const char fakeSourceTable[] = - "SOURCETABLE 200 OK\r\nServer: SparkPNT Caster/1.0\r\nContent-Type: " - "text/plain\r\nContent-Length: 96\r\n\r\nSTR;SparkBase;none;RTCM " - "3.0;none;none;none;none;none;none;none;none;none;none;none;B;N;none;none"; - - tcpServerClient[index]->write(fakeSourceTable, strlen(fakeSourceTable)); - - tcpServerStopClient(index); // Disconnect from client - } - else if (strnstr(response, "GET /", sizeof(response)) != NULL) // Mount point in header + // Use a semaphore to prevent tcpServerClientSendData from gatecrashing + if (tcpServerSemaphore == NULL) + tcpServerSemaphore = xSemaphoreCreateMutex(); // Create the mutex + + // Take the semaphore + if (xSemaphoreTake(tcpServerSemaphore, 50 / portTICK_PERIOD_MS) == pdPASS) { - // NTRIP Client is sending us their mount point. Begin sending RTCM. - if (settings.debugTcpServer) - systemPrintln("NTRIP Client connected - Sending ICY 200 OK"); - - char confirmConnection[] = "ICY 200 OK\r\n"; - tcpServerClient[index]->write(confirmConnection, strlen(confirmConnection)); + tcpServerSemaphoreHolder = "tcpServerUpdate"; + + // Read response from client + char response[512]; + int spot = 0; + while (tcpServerClient[index]->available()) + { + response[spot++] = tcpServerClient[index]->read(); + if (spot == sizeof(response)) + spot = 0; // Wrap + } + response[spot] = '\0'; // Terminate string + + if (strnstr(response, "GET / ", sizeof(response)) != NULL) // No mount point in header + { + if (settings.debugTcpServer) + systemPrintln("Mount point table requested."); + + // Respond with a single mountpoint + const char fakeSourceTable[] = + "SOURCETABLE 200 OK\r\nServer: SparkPNT Caster/1.0\r\nContent-Type: " + "text/plain\r\nContent-Length: 96\r\n\r\nSTR;SparkBase;none;RTCM " + "3.0;none;none;none;none;none;none;none;none;none;none;none;B;N;none;none"; + + tcpServerClient[index]->write(fakeSourceTable, strlen(fakeSourceTable)); + + tcpServerStopClient(index); // Disconnect from client + } + else if (strnstr(response, "GET /", sizeof(response)) != NULL) // Mount point in header + { + // NTRIP Client is sending us their mount point. Begin sending RTCM. + if (settings.debugTcpServer) + systemPrintln("NTRIP Client connected - Sending ICY 200 OK"); + + char confirmConnection[] = "ICY 200 OK\r\n"; + tcpServerClient[index]->write(confirmConnection, strlen(confirmConnection)); + } + else + { + // Unknown response + if (settings.debugTcpServer) + systemPrintf("Unknown response: %s\r\n", response); + } + + xSemaphoreGive(tcpServerSemaphore); } else { - // Unknown response - if (settings.debugTcpServer) - systemPrintf("Unknown response: %s\r\n", response); + systemPrintf("tcpServerUpdate could not get semaphore - held by %s\r\n", tcpServerSemaphoreHolder); } } // settings.enableNtripCaster == true || settings.baseCasterOverride == true diff --git a/Firmware/RTK_Everywhere/WebServer.ino b/Firmware/RTK_Everywhere/WebServer.ino index 666c0b74a..7588866b0 100644 --- a/Firmware/RTK_Everywhere/WebServer.ino +++ b/Firmware/RTK_Everywhere/WebServer.ino @@ -1093,16 +1093,8 @@ void webServerSetState(uint8_t newState) if (!online.rtc) systemPrintf("%s%s%s%s\r\n", asterisk, initialState, arrow, endingState); else - { // Timestamp the state change - // 1 2 - // 12345678901234567890123456 - // YYYY-mm-dd HH:MM:SS.xxxrn0 - struct tm timeinfo = rtc.getTimeStruct(); - char s[30]; - strftime(s, sizeof(s), "%Y-%m-%d %H:%M:%S", &timeinfo); - systemPrintf("%s%s%s%s, %s.%03ld\r\n", asterisk, initialState, arrow, endingState, s, rtc.getMillis()); - } + systemPrintf("%s%s%s%s, %s\r\n", asterisk, initialState, arrow, endingState, getTimeStamp()); } // Validate the state @@ -1343,6 +1335,8 @@ static esp_err_t ws_handler(httpd_req_t *req) for (int i = 0; i < ws_pkt.len; i++) { incomingSettings[incomingSettingsSpot++] = ws_pkt.payload[i]; + if (incomingSettingsSpot == AP_CONFIG_SETTING_SIZE) + systemPrintln("incomingSettings wrap-around. Increase AP_CONFIG_SETTING_SIZE"); incomingSettingsSpot %= AP_CONFIG_SETTING_SIZE; } timeSinceLastIncomingSetting = millis(); diff --git a/Firmware/RTK_Everywhere/menuFirmware.ino b/Firmware/RTK_Everywhere/menuFirmware.ino index 2f7382cc1..9f5b399c7 100644 --- a/Firmware/RTK_Everywhere/menuFirmware.ino +++ b/Firmware/RTK_Everywhere/menuFirmware.ino @@ -754,16 +754,8 @@ void otaSetState(uint8_t newState) if (!online.rtc) systemPrintf("%s%s%s%s\r\n", asterisk, initialState, arrow, endingState); else - { // Timestamp the state change - // 1 2 - // 12345678901234567890123456 - // YYYY-mm-dd HH:MM:SS.xxxrn0 - struct tm timeinfo = rtc.getTimeStruct(); - char s[30]; - strftime(s, sizeof(s), "%Y-%m-%d %H:%M:%S", &timeinfo); - systemPrintf("%s%s%s%s, %s.%03ld\r\n", asterisk, initialState, arrow, endingState, s, rtc.getMillis()); - } + systemPrintf("%s%s%s%s, %s\r\n", asterisk, initialState, arrow, endingState, getTimeStamp()); } // Validate the firmware update state diff --git a/Firmware/RTK_Everywhere/menuMain.ino b/Firmware/RTK_Everywhere/menuMain.ino index 1a93a755c..16b18f709 100644 --- a/Firmware/RTK_Everywhere/menuMain.ino +++ b/Firmware/RTK_Everywhere/menuMain.ino @@ -32,7 +32,11 @@ void terminalUpdate() // Push RTCM to GNSS module over I2C / SPI if (correctionLastSeen(CORR_USB)) + { gnss->pushRawData((uint8_t *)buffer, length); + sempParseNextBytes(rtcmParse, (uint8_t *)buffer, length); // Parse the data for RTCM1005/1006 + } + } // Does incoming data consist of RTCM correction messages @@ -51,7 +55,10 @@ void terminalUpdate() // Push RTCM to GNSS module over I2C / SPI if (correctionLastSeen(CORR_USB)) + { gnss->pushRawData((uint8_t *)buffer, length); + sempParseNextBytes(rtcmParse, (uint8_t *)buffer, length); // Parse the data for RTCM1005/1006 + } } else { diff --git a/Firmware/RTK_Everywhere/menuMessages.ino b/Firmware/RTK_Everywhere/menuMessages.ino index aa726b230..e6f7962d5 100644 --- a/Firmware/RTK_Everywhere/menuMessages.ino +++ b/Firmware/RTK_Everywhere/menuMessages.ino @@ -82,6 +82,15 @@ void menuLog() systemPrintln("Disabled"); } + if (settings.enableLogging == true) + { + systemPrint("9) Log file length alignment: "); + if (settings.alignedLogFiles == true) + systemPrintln("Enabled"); + else + systemPrintln("Disabled"); + } + systemPrintln("x) Exit"); int incoming = getUserInputNumber(); // Returns EXIT, TIMEOUT, or long @@ -90,15 +99,17 @@ void menuLog() { settings.enableLogging ^= 1; - // Reset the maximum logging time when logging is disabled to ensure that - // the next time logging is enabled that the maximum amount of data can be - // captured. - if (settings.enableLogging == false) - startLogTime_minutes = 0; + // Reset the start logging time when logging is enabled to ensure that + // data can be captured. + if (settings.enableLogging == true) + startLogTime_minutes = millis() / 1000L / 60; } else if (incoming == 2 && settings.enableLogging == true) { // Arbitrary 2 year limit. See https://github.com/sparkfun/SparkFun_RTK_Firmware/issues/86 + // Note: the 2 year limit is fine. But systemTime_minutes is based on millis(), and millis() + // will roll over every 2^32ms = ~50 days... + // TODO: use the GNSS epoch (uint32_t seconds plus uint32_t microseconds) to resolve this. getNewSetting("Enter max minutes before logging stops", 0, 60 * 24 * 365 * 2, &settings.maxLogTime_minutes); } else if (incoming == 3 && settings.enableLogging == true) @@ -120,7 +131,7 @@ void menuLog() else if (incoming == 6 && settings.enableLogging == true && settings.enableARPLogging == true) { // Arbitrary 10 minute limit - getNewSetting("Enter the ARP logging interval in seconds", 0, 60 * 10, &settings.ARPLoggingInterval_s); + getNewSetting("Enter the ARP logging interval in seconds", 1, 60 * 10, &settings.ARPLoggingInterval_s); } else if (incoming == 7) { @@ -130,6 +141,10 @@ void menuLog() { settings.enableNTPFile ^= 1; } + else if (incoming == 9 && settings.enableLogging == true) + { + settings.alignedLogFiles ^= 1; + } else if (incoming == 'x') break; else if (incoming == INPUT_RESPONSE_GETNUMBER_EXIT) @@ -289,11 +304,25 @@ bool beginLogging(const char *customFileName) sdUpdateFileCreateTimestamp(logFile); // Update the file to create time & date - startCurrentLogTime_minutes = millis() / 1000L / 60; // Mark now as start of logging - - // If it hasn't been done before, mark the initial start of logging for total run time - if (startLogTime_minutes == 0) - startLogTime_minutes = millis() / 1000L / 60; + // Calculate the time of the next log file change + nextLogTime_ms = 0; // Default to no limit + if ((settings.alignedLogFiles) && (settings.maxLogLength_minutes > 0)) + { + // Aligned logging is only possible if the interval is an integral fraction of 24 hours + if ((24 * 60 * 2) % settings.maxLogLength_minutes == 0) + { + // Calculate when the next log file should be opened - in millis() + unsigned long hoursAsMillis = rtc.getMillis() + (rtc.getSecond() * 1000) + + (rtc.getMinute() * 1000 * 60) + + (rtc.getHour(true) * 1000 * 60 * 60); + unsigned long maxLogLength_ms = (unsigned long)settings.maxLogLength_minutes * 60 * 1000; + unsigned long millisFromPreviousLog = hoursAsMillis % maxLogLength_ms; + unsigned long millisToNextLog = maxLogLength_ms - millisFromPreviousLog; + nextLogTime_ms = millis() + millisToNextLog; + } + } + if ((nextLogTime_ms == 0) && (settings.maxLogLength_minutes > 0)) // Non-aligned logging + nextLogTime_ms = millis() + ((unsigned long)settings.maxLogLength_minutes * 60 * 1000); // Add NMEA txt message with restart reason char rstReason[30]; @@ -378,6 +407,8 @@ bool beginLogging(const char *customFileName) currentDate); // textID, buffer, sizeOfBuffer, text logFile->println(nmeaMessage); + logFile->sync(); // Sync any partially written data + if (reuseLastLog == true) { systemPrintln("Appending last available log"); @@ -422,6 +453,7 @@ void endLogging(bool gotSemaphore, bool releaseSemaphore) char nmeaMessage[82]; // Max NMEA sentence length is 82 createNMEASentence(CUSTOM_NMEA_TYPE_PARSER_STATS, nmeaMessage, sizeof(nmeaMessage), parserStats); // textID, buffer, sizeOfBuffer, text + logFile->sync(); // Sync any partially written data logFile->println(nmeaMessage); logFile->sync(); @@ -436,7 +468,7 @@ void endLogging(bool gotSemaphore, bool releaseSemaphore) delete logFile; logFile = nullptr; - systemPrintln("Log file closed"); + systemPrintf("Log file closed @ %s\r\n", getTimeStamp()); // Release the semaphore if requested if (releaseSemaphore) diff --git a/Firmware/RTK_Everywhere/menuSystem.ino b/Firmware/RTK_Everywhere/menuSystem.ino index 4bb1a6134..52b8ec7f1 100644 --- a/Firmware/RTK_Everywhere/menuSystem.ino +++ b/Firmware/RTK_Everywhere/menuSystem.ino @@ -8,7 +8,7 @@ void menuSystem() systemPrintln(); systemPrintln("System Status"); - printTimeStamp(); + printTimeStamp(true); systemPrint("GNSS: "); if (online.gnss == true) @@ -801,6 +801,8 @@ void menuDebugSoftware() systemPrintf("40) Print LittleFS and settings management: %s\r\n", settings.debugSettings ? "Enabled" : "Disabled"); + systemPrintf("41) Halt on ESP_RST_PANIC: %s\r\n", + settings.haltOnPanic ? "Enabled" : "Disabled"); // Tasks systemPrint("50) Task Highwater Reporting: "); @@ -856,6 +858,8 @@ void menuDebugSoftware() else if (incoming == 40) settings.debugSettings ^= 1; + else if (incoming == 41) + settings.haltOnPanic ^= 1; else if (incoming == 50) settings.enableTaskReports ^= 1; @@ -1279,12 +1283,14 @@ void menuPeriodicPrint() else if (incoming == 20) { + systemPrint("Enter the new periodic print mask: "); int value = getUserInputNumber(); if ((value != INPUT_RESPONSE_GETNUMBER_EXIT) && (value != INPUT_RESPONSE_GETNUMBER_TIMEOUT)) settings.periodicDisplay = value; } else if (incoming == 21) { + systemPrint("Enter the new periodic display interval (s): "); int seconds = getUserInputNumber(); if ((seconds != INPUT_RESPONSE_GETNUMBER_EXIT) && (seconds != INPUT_RESPONSE_GETNUMBER_TIMEOUT)) settings.periodicDisplayInterval = seconds * 1000; diff --git a/Firmware/RTK_Everywhere/settings.h b/Firmware/RTK_Everywhere/settings.h index a4c84f1d7..b67e230ef 100644 --- a/Firmware/RTK_Everywhere/settings.h +++ b/Firmware/RTK_Everywhere/settings.h @@ -358,12 +358,12 @@ enum PeriodDisplayValues #ifdef COMPILE_NETWORK -// NTRIP Server data -typedef struct _NTRIP_SERVER_DATA +// NTRIP Server data - the array is declared volatile in NtripServer.ino +typedef struct { // Network connection used to push RTCM to NTRIP caster NetworkClient *networkClient; - volatile uint8_t state; + uint8_t state; // Count of bytes sent by the NTRIP server to the NTRIP caster uint32_t bytesSent; @@ -495,6 +495,7 @@ typedef enum FUNCTION_LOG_CLOSURE, FUNCTION_PRINT_FILE_LIST, FUNCTION_NTPEVENT, + FUNCTION_ARPWRITE, } SemaphoreFunction; // Print the base coordinates in different formats, depending on the type the user has entered @@ -725,11 +726,12 @@ struct Settings bool debugHttpClientState = false; // Debug the HTTP Client state machine // Log file - bool enableLogging = true; // If an SD card is present, log default sentences + bool alignedLogFiles = false; // If true, align log files as per #630 + bool enableLogging = true; // If an SD card is present, log default sentences bool enablePrintLogFileMessages = false; bool enablePrintLogFileStatus = true; int maxLogLength_minutes = 60 * 24; // Default to 24 hours - int maxLogTime_minutes = 60 * 24; // Default to 24 hours + int maxLogTime_minutes = 60 * 24; // Default to 24 hours // MQTT bool debugMqttClientData = false; // Debug the MQTT SPARTAN data flow @@ -832,6 +834,7 @@ struct Settings 1; // Read from GNSS and Write to circular buffer (SD, TCP, BT). 3 being the highest, and 0 being the lowest uint8_t gnssUartInterruptsCore = 1; // Core where hardware is started and interrupts are assigned to, 0=core, 1=Arduino + bool haltOnPanic = false; // Halt after beginVersion if the reset reason was panic uint8_t handleGnssDataTaskCore = 1; // Core where task should run, 0=core, 1=Arduino uint8_t handleGnssDataTaskPriority = 1; // Read from the circular buffer and dole out to end points (SD, TCP, BT). uint8_t i2cInterruptsCore = 1; // Core where hardware is started and interrupts are assigned to, 0=core, 1=Arduino @@ -1336,6 +1339,7 @@ const RTK_Settings_Entry rtkSettingsEntries[] = // g s x k 2 c h d d Type Qual Variable Name // Log file + { 1, 1, 0, 1, 1, 1, 0, 1, 1, _bool, 0, & settings.alignedLogFiles, "alignedLogFiles", }, { 1, 1, 0, 1, 1, 1, 0, 1, 1, _bool, 0, & settings.enableLogging, "enableLogging", }, { 0, 0, 0, 1, 1, 1, 0, 1, 1, _bool, 0, & settings.enablePrintLogFileMessages, "enablePrintLogFileMessages", }, { 0, 0, 0, 1, 1, 1, 0, 1, 1, _bool, 0, & settings.enablePrintLogFileStatus, "enablePrintLogFileStatus", }, @@ -1419,6 +1423,21 @@ const RTK_Settings_Entry rtkSettingsEntries[] = { 1, 1, 1, 1, 1, 1, 1, 1, 1, tNSMtPt, NTRIP_SERVER_MAX, & settings.ntripServer_MountPoint[0], "ntripServerMountPoint_", }, { 1, 1, 1, 1, 1, 1, 1, 1, 1, tNSMtPtPw, NTRIP_SERVER_MAX, & settings.ntripServer_MountPointPW[0], "ntripServerMountPointPW_", }, +// F +// a +// F c +// i a e +// n i c t +// W n u e +// e C s F t V P +// b o e a 2 o +// C m S c M s +// o m u e o T L t +// n a f t s o B c +// f n f E a r a a +// i d i v V i c n r +// g s x k 2 c h d d Type Qual Variable Name + // OS { 0, 0, 0, 1, 1, 1, 1, 1, 1, _uint8_t, 0, & settings.bluetoothInterruptsCore, "bluetoothInterruptsCore", }, { 0, 0, 0, 1, 1, 1, 1, 1, 1, _uint8_t, 0, & settings.btReadTaskCore, "btReadTaskCore", }, @@ -1431,6 +1450,7 @@ const RTK_Settings_Entry rtkSettingsEntries[] = { 0, 0, 0, 1, 1, 1, 1, 1, 1, _uint8_t, 0, & settings.gnssReadTaskCore, "gnssReadTaskCore", }, { 0, 0, 0, 1, 1, 1, 1, 1, 1, _uint8_t, 0, & settings.gnssReadTaskPriority, "gnssReadTaskPriority", }, { 0, 0, 0, 1, 1, 1, 1, 1, 1, _uint8_t, 0, & settings.gnssUartInterruptsCore, "gnssUartInterruptsCore", }, + { 0, 1, 0, 1, 1, 1, 1, 1, 1, _bool, 0, & settings.haltOnPanic, "haltOnPanic", }, { 0, 0, 0, 1, 1, 1, 1, 1, 1, _uint8_t, 0, & settings.handleGnssDataTaskCore, "handleGnssDataTaskCore", }, { 0, 0, 0, 1, 1, 1, 1, 1, 1, _uint8_t, 0, & settings.handleGnssDataTaskPriority, "handleGnssDataTaskPriority", }, { 0, 0, 0, 1, 1, 1, 1, 1, 1, _uint8_t, 0, & settings.i2cInterruptsCore, "i2cInterruptsCore", }, diff --git a/Firmware/RTK_Everywhere/support.ino b/Firmware/RTK_Everywhere/support.ino index 8d0f67a84..ef61e6625 100644 --- a/Firmware/RTK_Everywhere/support.ino +++ b/Firmware/RTK_Everywhere/support.ino @@ -302,6 +302,14 @@ InputResponse getUserInputString(char *userString, uint16_t stringSize, bool loc gnss->update(); // Regularly poll to get latest data + // Keep the ntripClient alive by pushing GPGGA. + // I'm not sure if we want / need to do this, but that's how it was when + // the GPGGA push was performed from processUart1Message from gnssReadTask. + // Doing it here keeps the user experience the same. It is safe to do it here + // because the loop is suspended and networkUpdate / ntripClientUpdate aren't + // being called. Maybe we _should_ call networkUpdate() here? Just sayin'... + pushGPGGA(nullptr); + // Keep processing NTP requests if (online.ethernetNTPServer) { @@ -475,23 +483,33 @@ void printElapsedTime(const char *title) #define TIMESTAMP_INTERVAL 1000 // Milliseconds +// Get the timestamp +const char *getTimeStamp() +{ + static char theTime[30]; + + // 1 2 3 + // 123456789012345678901234567890 + // YYYY-mm-dd HH:MM:SS.xxxrn0 + struct tm timeinfo = rtc.getTimeStruct(); + char timestamp[30]; + strftime(timestamp, sizeof(timestamp), "%Y-%m-%d %H:%M:%S", &timeinfo); + snprintf(theTime, sizeof(theTime), "%s.%03ld", timestamp, rtc.getMillis()); + + return (const char *)theTime; +} + // Print the timestamp -void printTimeStamp() +void printTimeStamp(bool always) { uint32_t currentMilliseconds; static uint32_t previousMilliseconds; // Timestamp the messages currentMilliseconds = millis(); - if ((currentMilliseconds - previousMilliseconds) >= TIMESTAMP_INTERVAL) + if (always || ((currentMilliseconds - previousMilliseconds) >= TIMESTAMP_INTERVAL)) { - // 1 2 3 - // 123456789012345678901234567890 - // YYYY-mm-dd HH:MM:SS.xxxrn0 - struct tm timeinfo = rtc.getTimeStruct(); - char timestamp[30]; - strftime(timestamp, sizeof(timestamp), "%Y-%m-%d %H:%M:%S", &timeinfo); - systemPrintf("%s.%03ld\r\n", timestamp, rtc.getMillis()); + systemPrintln(getTimeStamp()); // Select the next time to display the timestamp previousMilliseconds = currentMilliseconds;