diff --git a/Firmware/Dockerfile b/Firmware/Dockerfile index 0d77432d1..cb1eef806 100644 --- a/Firmware/Dockerfile +++ b/Firmware/Dockerfile @@ -75,7 +75,7 @@ RUN arduino-cli lib install "SparkFun MAX1704x Fuel Gauge Arduino Library"@1.0.4 RUN arduino-cli lib install "SparkFun u-blox GNSS v3"@3.1.10 RUN arduino-cli lib install "SparkFun Qwiic OLED Arduino Library"@1.0.13 RUN arduino-cli lib install SSLClientESP32@2.0.0 -RUN arduino-cli lib install "SparkFun Extensible Message Parser"@1.0.4 +RUN arduino-cli lib install "SparkFun Extensible Message Parser"@1.0.6 RUN arduino-cli lib install "SparkFun BQ40Z50 Battery Manager Arduino Library"@1.0.0 RUN arduino-cli lib install "ArduinoMqttClient"@0.1.8 RUN arduino-cli lib install "SparkFun u-blox PointPerfect Library"@1.11.4 diff --git a/Firmware/RTK_Everywhere/Base.ino b/Firmware/RTK_Everywhere/Base.ino index bc86bbf3b..ef12e7b20 100644 --- a/Firmware/RTK_Everywhere/Base.ino +++ b/Firmware/RTK_Everywhere/Base.ino @@ -2,21 +2,97 @@ Base.ino =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=*/ -// This function gets called when an RTCM packet passes parser check in processUart1Message() task -// Pass data along to NTRIP Server, or ESP-NOW radio -void processRTCM(uint8_t *rtcmData, uint16_t dataLength) +// Enough storage for two rounds of RTCM 1074,1084,1094,1124,1005 +// To help prevent the "no increase in file size" and "due to lack of RTCM" glitch: +// RTCM is stored in PSRAM by the processUart1Message task and written by sendRTCMToConsumers +const uint8_t rtcmConsumerBufferEntries = 16; +const uint16_t rtcmConsumerBufferEntrySize = 1032; // RTCM can be up to 1024 + 6 bytes +uint16_t rtcmConsumerBufferLengths[rtcmConsumerBufferEntries]; +uint8_t rtcmConsumerBufferHead; +uint8_t rtcmConsumerBufferTail; +uint8_t *rtcmConsumerBufferPtr = nullptr; + +// Allocate and initialize the rtcmConsumerBuffer +bool rtcmConsumerBufferAllocated() { - // Give this byte to the various possible transmission methods - for (int x = 0; x < dataLength; x++) + if (rtcmConsumerBufferPtr == nullptr) { - for (int serverIndex = 0; serverIndex < NTRIP_SERVER_MAX; serverIndex++) - ntripServerProcessRTCM(serverIndex, rtcmData[x]); + rtcmConsumerBufferPtr = (uint8_t *)rtkMalloc( + (size_t)rtcmConsumerBufferEntrySize * (size_t)rtcmConsumerBufferEntries, + "ntripBuffer"); + for (uint8_t i = 0; i < rtcmConsumerBufferEntries; i++) + rtcmConsumerBufferLengths[i] = 0; + rtcmConsumerBufferHead = 0; + rtcmConsumerBufferTail = 0; + } + if (rtcmConsumerBufferPtr == nullptr) + { + systemPrintln("rtcmConsumerBuffer malloc failed!"); + return false; + } + + return true; +} + +// Store each RTCM message in a PSRAM buffer +// The messages are written to the servers by sendRTCMToConsumers +void storeRTCMForConsumers(uint8_t *rtcmData, uint16_t dataLength) +{ + if (!rtcmConsumerBufferAllocated()) + return; + + // Check if a buffer is available + uint8_t buffersInUse = rtcmConsumerBufferHead - rtcmConsumerBufferTail; + if (buffersInUse >= rtcmConsumerBufferEntries) // Wrap if Tail is > Head + buffersInUse += rtcmConsumerBufferEntries; + if (buffersInUse < (rtcmConsumerBufferEntries - 1)) + { + uint8_t *dest = rtcmConsumerBufferPtr; + dest += (size_t)rtcmConsumerBufferEntrySize * (size_t)rtcmConsumerBufferHead; + memcpy(dest, rtcmData, dataLength); // Store the RTCM + rtcmConsumerBufferLengths[rtcmConsumerBufferHead] = dataLength; // Store the length + rtcmConsumerBufferHead++; // Increment the Head + rtcmConsumerBufferHead %= rtcmConsumerBufferEntries; // Wrap + } + else + { + if (settings.debugNtripServerRtcm && (!inMainMenu)) + systemPrintln("rtcmConsumerBuffer full. RTCM lost"); } +} + +// Send the stored RTCM to consumers: ntripServer, LoRa and ESP-NOW +void sendRTCMToConsumers() +{ + if (!rtcmConsumerBufferAllocated()) + return; + + while (rtcmConsumerBufferHead != rtcmConsumerBufferTail) + { + uint8_t *dest = rtcmConsumerBufferPtr; + dest += (size_t)rtcmConsumerBufferEntrySize * (size_t)rtcmConsumerBufferTail; + + // NTRIP Server + for (int serverIndex = 0; serverIndex < NTRIP_SERVER_MAX; serverIndex++) + ntripServerSendRTCM(serverIndex, dest, rtcmConsumerBufferLengths[rtcmConsumerBufferTail]); - for (int x = 0; x < dataLength; x++) - espNowProcessRTCM(rtcmData[x]); + // LoRa + loraProcessRTCM(dest, rtcmConsumerBufferLengths[rtcmConsumerBufferTail]); - loraProcessRTCM(rtcmData, dataLength); + // ESP-NOW + for (uint16_t x = 0; x < rtcmConsumerBufferLengths[rtcmConsumerBufferTail]; x++) + espNowProcessRTCM(dest[x]); + + rtcmConsumerBufferTail++; // Increment the Tail + rtcmConsumerBufferTail %= rtcmConsumerBufferEntries; // Wrap + } +} + +// This function gets called when an RTCM packet passes parser check in processUart1Message() task +// Store data ready to be passed along to NTRIP Server, or ESP-NOW radio +void processRTCM(uint8_t *rtcmData, uint16_t dataLength) +{ + storeRTCMForConsumers(rtcmData, dataLength); rtcmLastPacketSent = millis(); rtcmPacketsSent++; diff --git a/Firmware/RTK_Everywhere/Begin.ino b/Firmware/RTK_Everywhere/Begin.ino index b5b2d0b2b..a2ec06b0f 100644 --- a/Firmware/RTK_Everywhere/Begin.ino +++ b/Firmware/RTK_Everywhere/Begin.ino @@ -357,6 +357,7 @@ void beginBoard() pin_modeButton = 0; // 24, D2 : Status LED pin_baseStatusLED = 2; + //pin_debug = 2; // On EVK we can use the Status LED for debug // 29, D5 : GNSS TP via 74LVC4066 switch pin_GNSS_TimePulse = 5; // 14, D12 : I2C1 SDA via 74LVC4066 switch @@ -421,6 +422,10 @@ void beginBoard() pinMode(pin_baseStatusLED, OUTPUT); baseStatusLedOff(); + DMW_if systemPrintf("pin_debug: %d\r\n", pin_debug); + pinMode(pin_debug, OUTPUT); + pinDebugOff(); + DMW_if systemPrintf("pin_Cellular_Network_Indicator: %d\r\n", pin_Cellular_Network_Indicator); pinMode(pin_Cellular_Network_Indicator, INPUT); diff --git a/Firmware/RTK_Everywhere/Developer.ino b/Firmware/RTK_Everywhere/Developer.ino index d10c41f16..a9fdcf359 100644 --- a/Firmware/RTK_Everywhere/Developer.ino +++ b/Firmware/RTK_Everywhere/Developer.ino @@ -238,7 +238,8 @@ void ntripClientSettingsChanged() {} #ifndef COMPILE_NTRIP_SERVER bool ntripServerIsCasting(int serverIndex) {return false;} void ntripServerPrintStatus(int serverIndex) {systemPrintf("**NTRIP Server %d not compiled**\r\n", serverIndex);} -void ntripServerProcessRTCM(int serverIndex, uint8_t incoming) {} +void ntripServerProcessRTCM(uint8_t *rtcmData, uint16_t dataLength) {} +void ntripServerSendRTCM(int serverIndex, uint8_t *rtcmData, uint16_t dataLength) {} void ntripServerStop(int serverIndex, bool shutdown) {online.ntripServer[serverIndex] = false;} void ntripServerUpdate() {} void ntripServerValidateTables() {} diff --git a/Firmware/RTK_Everywhere/LED.ino b/Firmware/RTK_Everywhere/LED.ino index 2800611cf..042538944 100644 --- a/Firmware/RTK_Everywhere/LED.ino +++ b/Firmware/RTK_Everywhere/LED.ino @@ -50,6 +50,18 @@ void baseStatusLedOff() digitalWrite(pin_baseStatusLED, LOW); } +void pinDebugOn() +{ + if (pin_debug != PIN_UNDEFINED) + digitalWrite(pin_debug, HIGH); +} + +void pinDebugOff() +{ + if (pin_debug != PIN_UNDEFINED) + digitalWrite(pin_debug, LOW); +} + void baseStatusLedBlink() { if (pin_baseStatusLED != PIN_UNDEFINED) diff --git a/Firmware/RTK_Everywhere/NtripServer.ino b/Firmware/RTK_Everywhere/NtripServer.ino index f3e882abe..2f45ef222 100644 --- a/Firmware/RTK_Everywhere/NtripServer.ino +++ b/Firmware/RTK_Everywhere/NtripServer.ino @@ -122,6 +122,213 @@ NtripServer.ino =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=*/ +#ifdef COMPILE_NETWORK + +// NTRIP Server data +const TickType_t serverSemaphore_shortWait_ms = 10 / portTICK_PERIOD_MS; +const TickType_t serverSemaphore_longWait_ms = 100 / portTICK_PERIOD_MS; +typedef struct +{ + // Network connection used to push RTCM to NTRIP caster + NetworkClient *networkClient; + volatile uint8_t state; + + // Count of bytes sent by the NTRIP server to the NTRIP caster + volatile uint32_t bytesSent; + + // Throttle the time between connection attempts + // ms - Max of 4,294,967,295 or 4.3M seconds or 71,000 minutes or 1193 hours or 49 days between attempts + volatile uint32_t connectionAttemptTimeout; + volatile int connectionAttempts; // Count the number of connection attempts between restarts + + // NTRIP server timer usage: + // * Reconnection delay + // * Measure the connection response time + // * Receive RTCM correction data timeout + // * Monitor last RTCM byte received for frame counting + volatile uint32_t timer; + volatile uint32_t startTime; + volatile int connectionAttemptsTotal; // Count the number of connection attempts absolutely + + // Better debug printing by ntripServerProcessRTCM + volatile uint32_t rtcmBytesSent; + volatile uint32_t previousMilliseconds; + + + // Protect all methods that manipulate timer with a mutex - to avoid race conditions + // Also protect the write from connected checks + SemaphoreHandle_t serverSemaphore = NULL; + + unsigned long millisSinceTimer() + { + unsigned long retVal = 0; + if (serverSemaphore == NULL) + serverSemaphore = xSemaphoreCreateMutex(); + if (xSemaphoreTake(serverSemaphore, serverSemaphore_shortWait_ms) == pdPASS) + { + retVal = millis() - timer; + xSemaphoreGive(serverSemaphore); + } + return retVal; + } + + unsigned long millisSinceStartTime() + { + unsigned long retVal = 0; + if (serverSemaphore == NULL) + serverSemaphore = xSemaphoreCreateMutex(); + if (xSemaphoreTake(serverSemaphore, serverSemaphore_shortWait_ms) == pdPASS) + { + retVal = millis() - startTime; + xSemaphoreGive(serverSemaphore); + } + return retVal; + } + + void updateTimerAndBytesSent() + { + if (serverSemaphore == NULL) + serverSemaphore = xSemaphoreCreateMutex(); + if (xSemaphoreTake(serverSemaphore, serverSemaphore_shortWait_ms) == pdPASS) + { + bytesSent = bytesSent + 1; + rtcmBytesSent = rtcmBytesSent + 1; + timer = millis(); + xSemaphoreGive(serverSemaphore); + } + } + + void updateTimerAndBytesSent(uint16_t dataLength) + { + if (serverSemaphore == NULL) + serverSemaphore = xSemaphoreCreateMutex(); + if (xSemaphoreTake(serverSemaphore, serverSemaphore_shortWait_ms) == pdPASS) + { + bytesSent = bytesSent + dataLength; + rtcmBytesSent = rtcmBytesSent + dataLength; + timer = millis(); + xSemaphoreGive(serverSemaphore); + } + } + + bool checkBytesSentAndReset(uint32_t timerLimit, uint32_t *totalBytesSent) + { + bool retVal = false; + if (serverSemaphore == NULL) + serverSemaphore = xSemaphoreCreateMutex(); + if (xSemaphoreTake(serverSemaphore, serverSemaphore_shortWait_ms) == pdPASS) + { + if (((millis() - timer) > timerLimit) && (bytesSent > 0)) + { + retVal = true; + *totalBytesSent = bytesSent; + bytesSent = 0; + } + xSemaphoreGive(serverSemaphore); + } + return retVal; + } + + unsigned long getUptime() + { + unsigned long retVal = 0; + if (serverSemaphore == NULL) + serverSemaphore = xSemaphoreCreateMutex(); + if (xSemaphoreTake(serverSemaphore, serverSemaphore_shortWait_ms) == pdPASS) + { + retVal = timer - startTime; + xSemaphoreGive(serverSemaphore); + } + return retVal; + } + + void setTimerToMillis() + { + if (serverSemaphore == NULL) + serverSemaphore = xSemaphoreCreateMutex(); + if (xSemaphoreTake(serverSemaphore, serverSemaphore_shortWait_ms) == pdPASS) + { + timer = millis(); + xSemaphoreGive(serverSemaphore); + } + } + + bool checkConnectionAttemptTimeout() + { + bool retVal = false; + if (serverSemaphore == NULL) + serverSemaphore = xSemaphoreCreateMutex(); + if (xSemaphoreTake(serverSemaphore, serverSemaphore_shortWait_ms) == pdPASS) + { + if ((millis() - timer) >= connectionAttemptTimeout) + { + retVal = true; + } + xSemaphoreGive(serverSemaphore); + } + return retVal; + } + + bool networkClientConnected(bool assumeConnected) + { + bool retVal = assumeConnected; + if (serverSemaphore == NULL) + serverSemaphore = xSemaphoreCreateMutex(); + if (xSemaphoreTake(serverSemaphore, serverSemaphore_longWait_ms) == pdPASS) + { + retVal = (bool)networkClient->connected(); + xSemaphoreGive(serverSemaphore); + } + return retVal; + } + + size_t networkClientWrite(const uint8_t *buf, size_t size) + { + size_t retVal = 0; + if (serverSemaphore == NULL) + serverSemaphore = xSemaphoreCreateMutex(); + if (xSemaphoreTake(serverSemaphore, serverSemaphore_longWait_ms) == pdPASS) + { + retVal = networkClient->write(buf, size); + xSemaphoreGive(serverSemaphore); + } + return retVal; + } + + void networkClientAbsorb() + { + static uint8_t *buffer = nullptr; + const size_t bufferSize = 256; + if (buffer == nullptr) + buffer = (uint8_t *)rtkMalloc(bufferSize, "networkClientAbsorb"); + if (serverSemaphore == NULL) + serverSemaphore = xSemaphoreCreateMutex(); + if (xSemaphoreTake(serverSemaphore, serverSemaphore_shortWait_ms) == pdPASS) + { + if (buffer == nullptr) + { + while (networkClient->available()) + networkClient->read(); // Absorb any unwanted incoming traffic + } + else + { + while (networkClient->available()) + { + int bytesRead = networkClient->read(buffer, bufferSize); // Absorb any unwanted incoming traffic + if ((bytesRead > 0) && settings.debugNtripServerRtcm && (!inMainMenu)) + { + systemPrintln("Data received from networkClient:"); + dumpBuffer(buffer, bytesRead); + } + } + } + xSemaphoreGive(serverSemaphore); + } + } +} NTRIP_SERVER_DATA; + +#endif // COMPILE_NETWORK + #ifdef COMPILE_NTRIP_SERVER //---------------------------------------- @@ -407,9 +614,9 @@ void ntripServerPrintStatus(int serverIndex) } //---------------------------------------- -// This function gets called as each RTCM byte comes in +// This function gets called as each complete RTCM message comes in //---------------------------------------- -void ntripServerProcessRTCM(int serverIndex, uint8_t incoming) +void ntripServerSendRTCM(int serverIndex, uint8_t *rtcmData, uint16_t dataLength) { NTRIP_SERVER_DATA *ntripServer = &ntripServerArray[serverIndex]; @@ -434,18 +641,22 @@ void ntripServerProcessRTCM(int serverIndex, uint8_t incoming) } // If we have not gotten new RTCM bytes for a period of time, assume end of frame - if (ntripServer->checkBytesSentAndReset(100) && (!inMainMenu) && settings.debugNtripServerRtcm) + uint32_t totalBytesSent; + if (ntripServer->checkBytesSentAndReset(100, &totalBytesSent) && (!inMainMenu) && settings.debugNtripServerRtcm) systemPrintf("NTRIP Server %d transmitted %d RTCM bytes to Caster\r\n", serverIndex, - ntripServer->bytesSent); + totalBytesSent); - if (ntripServer->networkClient && ntripServer->networkClient->connected()) + if (ntripServer->networkClient && ntripServer->networkClientConnected(true)) { - if (ntripServer->networkClient->write(incoming) == 1) // Send this byte to socket + unsigned long entryTime = millis(); + + //pinDebugOn(); + if (ntripServer->networkClientWrite(rtcmData, dataLength) == dataLength) // Send this byte to socket { - ntripServer->updateTimerAndBytesSent(); + //pinDebugOff(); + ntripServer->updateTimerAndBytesSent(dataLength); netOutgoingRTCM = true; - while (ntripServer->networkClient->available()) - ntripServer->networkClient->read(); // Absorb any unwanted incoming traffic + ntripServer->networkClientAbsorb(); // Absorb any unwanted incoming traffic } // Failed to write the data else @@ -455,6 +666,14 @@ void ntripServerProcessRTCM(int serverIndex, uint8_t incoming) systemPrintf("NTRIP Server %d broken connection to %s\r\n", serverIndex, settings.ntripServer_CasterHost[serverIndex]); } + //pinDebugOff(); + + if (((millis() - entryTime) > settings.networkClientWriteTimeout_ms) && settings.debugNtripServerRtcm && (!inMainMenu)) + { + if (pin_debug != PIN_UNDEFINED) + systemPrint(debugMessagePrefix); + systemPrintf("ntripServer write took %ldms\r\n", millis() - entryTime); + } } } @@ -559,7 +778,7 @@ void ntripServerStop(int serverIndex, bool shutdown) if (ntripServer->networkClient) { // Break the NTRIP server connection if necessary - if (ntripServer->networkClient->connected()) + if (ntripServer->networkClientConnected(true)) ntripServer->networkClient->stop(); // Free the NTRIP server resources @@ -781,6 +1000,13 @@ void ntripServerUpdate(int serverIndex) online.ntripServer[serverIndex] = true; ntripServer->startTime = millis(); ntripServerSetState(serverIndex, NTRIP_SERVER_CASTING); + + // Now we are ready to start casting, decrease timeout to networkClientWriteTimeout_ms + // The default timeout is WIFI_CLIENT_DEF_CONN_TIMEOUT_MS (3000) + // Each write will retry up to WIFI_CLIENT_MAX_WRITE_RETRY (10) times + // NetworkClient uses the same _timeout for both the initial connection and + // subsequent writes. The trick is to setConnectionTimeout after we are connected + ntripServer->networkClient->setConnectionTimeout(settings.networkClientWriteTimeout_ms); } // Look for '401 Unauthorized' @@ -813,7 +1039,7 @@ void ntripServerUpdate(int serverIndex) // NTRIP server authorized to send RTCM correction data to NTRIP caster case NTRIP_SERVER_CASTING: // Check for a broken connection - if (!ntripServer->networkClient->connected()) + if (!ntripServer->networkClientConnected(true)) { // Broken connection, retry the NTRIP connection systemPrintf("Connection to NTRIP Caster %d - %s was lost\r\n", serverIndex, @@ -823,6 +1049,8 @@ void ntripServerUpdate(int serverIndex) else if (ntripServer->millisSinceTimer() > (10 * 1000)) { // GNSS stopped sending RTCM correction data + if (pin_debug != PIN_UNDEFINED) + systemPrint(debugMessagePrefix); systemPrintf("NTRIP Server %d breaking connection to %s due to lack of RTCM data!\r\n", serverIndex, settings.ntripServer_CasterHost[serverIndex]); ntripServerRestart(serverIndex); diff --git a/Firmware/RTK_Everywhere/RTK_Everywhere.ino b/Firmware/RTK_Everywhere/RTK_Everywhere.ino index b76007af6..10cd71b9c 100644 --- a/Firmware/RTK_Everywhere/RTK_Everywhere.ino +++ b/Firmware/RTK_Everywhere/RTK_Everywhere.ino @@ -228,10 +228,13 @@ const uint16_t HTTPS_PORT = 443; #define SECONDS_IN_AN_HOUR (MINUTES_IN_AN_HOUR * SECONDS_IN_A_MINUTE) #define SECONDS_IN_A_DAY (HOURS_IN_A_DAY * SECONDS_IN_AN_HOUR) +const char *debugMessagePrefix = "# => "; // Something ~unique and easy to trigger on + // Hardware connections //=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= // These pins are set in beginBoard() #define PIN_UNDEFINED -1 +int pin_debug = PIN_UNDEFINED; // LED on EVK int pin_batteryStatusLED = PIN_UNDEFINED; // LED on Torch int pin_baseStatusLED = PIN_UNDEFINED; // LED on EVK int pin_bluetoothStatusLED = PIN_UNDEFINED; // LED on Torch @@ -619,7 +622,7 @@ volatile bool inDirectConnectMode = false; // Global state to indicate if GNSS/L #define SERIAL_SIZE_TX 512 uint8_t wBuffer[SERIAL_SIZE_TX]; // Buffer for writing from incoming SPP to F9P -const int btReadTaskStackSize = 4000; +const int btReadTaskStackSize = 3000; // Array of start-of-sentence offsets into the ring buffer #define AMOUNT_OF_RING_BUFFER_DATA_TO_DISCARD (settings.gnssHandlerBufferSize >> 2) @@ -1714,7 +1717,11 @@ void logUpdate() else { if ((settings.enablePrintLogFileStatus) && (!inMainMenu)) + { + if (pin_debug != PIN_UNDEFINED) + systemPrint(debugMessagePrefix); systemPrintf("No increase in file size: %llu -> %llu\r\n", lastLogSize, logFileSize); + } logIncreasing = false; endSD(false, true); // alreadyHaveSemaphore, releaseSemaphore diff --git a/Firmware/RTK_Everywhere/Tasks.ino b/Firmware/RTK_Everywhere/Tasks.ino index 230406b2e..4b8153f73 100644 --- a/Firmware/RTK_Everywhere/Tasks.ino +++ b/Firmware/RTK_Everywhere/Tasks.ino @@ -357,7 +357,15 @@ void gnssReadTask(void *e) reportFatalError("Failed to initialize the parser"); if (settings.debugGnss) - sempEnableDebugOutput(rtkParse); + { + sempEnableDebugOutput(rtkParse); // Standard debug on Serial + //sempEnableDebugOutput(rtkParse, &Serial, true); // Verbose debug + } + + // Abort NMEA and Unicore Hash on non-printable character + // Help faster recovery from UART errors on EVK + sempAbortNmeaOnNonPrintable(rtkParse); + sempAbortHashOnNonPrintable(rtkParse); bool sbfParserNeeded = present.gnss_mosaicX5; bool spartnParserNeeded = present.gnss_mosaicX5 && (productVariant != RTK_FLEX); @@ -450,20 +458,33 @@ void gnssReadTask(void *e) while (serialGNSS->available()) { // Read the data from UART1 - uint8_t incomingData[500]; + static uint8_t incomingData[256]; int bytesIncoming = serialGNSS->read(incomingData, sizeof(incomingData)); totalRxByteCount += bytesIncoming; + if ((bytesIncoming < 0) || (bytesIncoming > sizeof(incomingData))) + { + if (settings.debugGnss) + systemPrintf("gnssReadTask: bytesIncoming = %d\r\n", bytesIncoming); + } + for (int x = 0; x < bytesIncoming; x++) { // Update the parser state based on the incoming byte // On mosaic-X5, pass the byte to sbfParse. On all other platforms, pass it straight to rtkParse - sempParseNextByte(sbfParserNeeded ? sbfParse : rtkParse, incomingData[x]); + if (!sbfParserNeeded) + { + //pinDebugOn(); + sempParseNextByte(rtkParse, incomingData[x]); + //pinDebugOff(); + } // See notes above. On the mosaic-X5, check that the incoming SBF blocks have expected IDs and // lengths to help prevent raw L-Band data being misidentified as SBF - if (sbfParserNeeded) + else // if (sbfParserNeeded) { + sempParseNextByte(sbfParse, incomingData[x]); + SEMP_SCRATCH_PAD *scratchPad = (SEMP_SCRATCH_PAD *)sbfParse->scratchPad; // Check if this is Length MSB @@ -884,7 +905,9 @@ void processUart1Message(SEMP_PARSE_STATE *parse, uint16_t type) if (inBaseMode() && type == RTK_RTCM_PARSER_INDEX) { // Pass data along to NTRIP Server, ESP-NOW radio, or LoRa + //pinDebugOn(); processRTCM(parse->buffer, parse->length); + //pinDebugOff(); } // Determine if we are using the PPL - UM980, LG290P, or mosaic-X5 @@ -1258,8 +1281,9 @@ void processUart1Message(SEMP_PARSE_STATE *parse, uint16_t type) } else { - systemPrintf("processUart1Message could not get ringBuffer semaphore - held by %s\r\n", - ringBufferSemaphoreHolder); + if (settings.debugGnss && (!inMainMenu)) + systemPrintf("processUart1Message could not get ringBuffer semaphore - held by %s\r\n", + ringBufferSemaphoreHolder); } } @@ -1350,6 +1374,21 @@ void handleGnssDataTask(void *e) systemPrintln("handleGnssDataTask running"); } + //---------------------------------------------------------------------- + // Send RTCM to consumers + // + // RTCM has its own storage in rtcmConsumerBuffer (Base.ino) + // It does not use the main ringBuffer + // But we do the writing here so all traffic generated in the same place + //---------------------------------------------------------------------- + + startMillis = millis(); + + sendRTCMToConsumers(); + + if ((millis() - startMillis) > settings.networkClientWriteTimeout_ms) + slowConsumer = "RTCM Consumers"; + usedSpace = 0; // Use a semaphore to prevent handleGnssDataTask from gatecrashing @@ -1565,6 +1604,8 @@ void handleGnssDataTask(void *e) { markSemaphore(FUNCTION_WRITESD); + //pinDebugOn(); + do // Do the SD write in a do loop so we can break out if needed { if (settings.enablePrintSDBuffers && (!inMainMenu)) @@ -1722,6 +1763,9 @@ void handleGnssDataTask(void *e) } } while (0); + + //pinDebugOff(); + xSemaphoreGive(sdCardSemaphore); } // End sdCardSemaphore else @@ -1788,8 +1832,9 @@ void handleGnssDataTask(void *e) } else { - systemPrintf("handleGnssDataTask could not get ringBuffer semaphore - held by %s\r\n", - ringBufferSemaphoreHolder); + if (settings.debugGnss && (!inMainMenu)) + systemPrintf("handleGnssDataTask could not get ringBuffer semaphore - held by %s\r\n", + ringBufferSemaphoreHolder); } //---------------------------------------------------------------------- diff --git a/Firmware/RTK_Everywhere/TcpServer.ino b/Firmware/RTK_Everywhere/TcpServer.ino index ab86d2923..b4f115834 100644 --- a/Firmware/RTK_Everywhere/TcpServer.ino +++ b/Firmware/RTK_Everywhere/TcpServer.ino @@ -120,7 +120,7 @@ static const char * tcpServerName; static volatile uint8_t tcpServerClientConnected; static volatile uint8_t tcpServerClientDataSent; static volatile uint8_t tcpServerClientSendingData; -static uint32_t tcpServerClientTimer[TCP_SERVER_MAX_CLIENTS]; +static volatile uint32_t tcpServerClientTimer[TCP_SERVER_MAX_CLIENTS]; static volatile uint8_t tcpServerClientWriteError; static NetworkClient *tcpServerClient[TCP_SERVER_MAX_CLIENTS]; static IPAddress tcpServerClientIpAddress[TCP_SERVER_MAX_CLIENTS]; @@ -168,9 +168,12 @@ int32_t tcpServerClientSendData(int index, uint8_t *data, uint16_t length) length = tcpServerClient[index]->write(data, length); if (length > 0) { - // Update the data sent flag when data successfully sent + // Update the data sent flag and timer when data successfully sent if (length > 0) + { tcpServerClientDataSent = tcpServerClientDataSent | (1 << index); + tcpServerClientTimer[index] = millis(); + } if ((settings.debugTcpServer || PERIODIC_DISPLAY(PD_TCP_SERVER_CLIENT_DATA)) && (!inMainMenu)) systemPrintf("%s wrote %d bytes to %s\r\n", tcpServerName, length, diff --git a/Firmware/RTK_Everywhere/menuSystem.ino b/Firmware/RTK_Everywhere/menuSystem.ino index a551bca71..e7836bd63 100644 --- a/Firmware/RTK_Everywhere/menuSystem.ino +++ b/Firmware/RTK_Everywhere/menuSystem.ino @@ -648,7 +648,8 @@ void menuDebugHardware() int newDelay = getUserInputNumber(); // Returns EXIT, TIMEOUT, or long if ((newDelay != INPUT_RESPONSE_GETNUMBER_EXIT) && (newDelay != INPUT_RESPONSE_GETNUMBER_TIMEOUT)) { - settings.cliBlePrintDelay_ms = newDelay; + if ((newDelay >= 0) && (newDelay <= 1000)) + settings.cliBlePrintDelay_ms = newDelay; } } @@ -715,6 +716,8 @@ void menuDebugNetwork() systemPrint("11) Print network layer status: "); systemPrintf("%s\r\n", settings.printNetworkStatus ? "Enabled" : "Disabled"); + systemPrintf("12) NetworkClient write timeout: %ldms\r\n", settings.networkClientWriteTimeout_ms); + // NTP systemPrint("20) Debug NTP: "); systemPrintf("%s\r\n", settings.debugNtp ? "Enabled" : "Disabled"); @@ -773,6 +776,16 @@ void menuDebugNetwork() settings.debugNetworkLayer ^= 1; else if (incoming == 11) settings.printNetworkStatus ^= 1; + else if (incoming == 12) + { + systemPrintf("Enter NetworkClient timeout (%d to %d): ", 100, 3000); + int newDelay = getUserInputNumber(); // Returns EXIT, TIMEOUT, or long + if ((newDelay != INPUT_RESPONSE_GETNUMBER_EXIT) && (newDelay != INPUT_RESPONSE_GETNUMBER_TIMEOUT)) + { + if ((newDelay >= 100) && (newDelay <= 3000)) + settings.networkClientWriteTimeout_ms = newDelay; + } + } else if (incoming == 20) settings.debugNtp ^= 1; else if (incoming == 21) diff --git a/Firmware/RTK_Everywhere/settings.h b/Firmware/RTK_Everywhere/settings.h index 383e48e35..0aed5ee85 100644 --- a/Firmware/RTK_Everywhere/settings.h +++ b/Firmware/RTK_Everywhere/settings.h @@ -444,141 +444,6 @@ enum PeriodDisplayValues #define PERIODIC_SETTING(x) (settings.periodicDisplay & PERIODIC_MASK(x)) #define PERIODIC_TOGGLE(x) settings.periodicDisplay = settings.periodicDisplay ^ PERIODIC_MASK(x) -#ifdef COMPILE_NETWORK - -// NTRIP Server data -typedef struct -{ - // Network connection used to push RTCM to NTRIP caster - NetworkClient *networkClient; - volatile uint8_t state; - - // Count of bytes sent by the NTRIP server to the NTRIP caster - volatile uint32_t bytesSent; - - // Throttle the time between connection attempts - // ms - Max of 4,294,967,295 or 4.3M seconds or 71,000 minutes or 1193 hours or 49 days between attempts - volatile uint32_t connectionAttemptTimeout; - volatile int connectionAttempts; // Count the number of connection attempts between restarts - - // NTRIP server timer usage: - // * Reconnection delay - // * Measure the connection response time - // * Receive RTCM correction data timeout - // * Monitor last RTCM byte received for frame counting - volatile uint32_t timer; - volatile uint32_t startTime; - volatile int connectionAttemptsTotal; // Count the number of connection attempts absolutely - - // Better debug printing by ntripServerProcessRTCM - volatile uint32_t rtcmBytesSent; - volatile uint32_t previousMilliseconds; - - - // Protect all methods that manipulate timer with a mutex - to avoid race conditions - // Remember that data is pushed to the servers by - // gnssReadTask -> processUart1Message -> processRTCM -> ntripServerProcessRTCM - SemaphoreHandle_t serverSemaphore = NULL; - - unsigned long millisSinceTimer() - { - unsigned long retVal = 0; - if (serverSemaphore == NULL) - serverSemaphore = xSemaphoreCreateMutex(); - if (xSemaphoreTake(serverSemaphore, 10 / portTICK_PERIOD_MS) == pdPASS) - { - retVal = millis() - timer; - xSemaphoreGive(serverSemaphore); - } - return retVal; - } - - unsigned long millisSinceStartTime() - { - unsigned long retVal = 0; - if (serverSemaphore == NULL) - serverSemaphore = xSemaphoreCreateMutex(); - if (xSemaphoreTake(serverSemaphore, 10 / portTICK_PERIOD_MS) == pdPASS) - { - retVal = millis() - startTime; - xSemaphoreGive(serverSemaphore); - } - return retVal; - } - - void updateTimerAndBytesSent() - { - if (serverSemaphore == NULL) - serverSemaphore = xSemaphoreCreateMutex(); - if (xSemaphoreTake(serverSemaphore, 10 / portTICK_PERIOD_MS) == pdPASS) - { - bytesSent = bytesSent + 1; - rtcmBytesSent = rtcmBytesSent + 1; - timer = millis(); - xSemaphoreGive(serverSemaphore); - } - } - - bool checkBytesSentAndReset(uint32_t timerLimit) - { - bool retVal = false; - if (serverSemaphore == NULL) - serverSemaphore = xSemaphoreCreateMutex(); - if (xSemaphoreTake(serverSemaphore, 10 / portTICK_PERIOD_MS) == pdPASS) - { - if (((millis() - timer) > timerLimit) && (bytesSent > 0)) - { - retVal = true; - bytesSent = 0; - } - xSemaphoreGive(serverSemaphore); - } - return retVal; - } - - unsigned long getUptime() - { - unsigned long retVal = 0; - if (serverSemaphore == NULL) - serverSemaphore = xSemaphoreCreateMutex(); - if (xSemaphoreTake(serverSemaphore, 10 / portTICK_PERIOD_MS) == pdPASS) - { - retVal = timer - startTime; - xSemaphoreGive(serverSemaphore); - } - return retVal; - } - - void setTimerToMillis() - { - if (serverSemaphore == NULL) - serverSemaphore = xSemaphoreCreateMutex(); - if (xSemaphoreTake(serverSemaphore, 10 / portTICK_PERIOD_MS) == pdPASS) - { - timer = millis(); - xSemaphoreGive(serverSemaphore); - } - } - - bool checkConnectionAttemptTimeout() - { - bool retVal = false; - if (serverSemaphore == NULL) - serverSemaphore = xSemaphoreCreateMutex(); - if (xSemaphoreTake(serverSemaphore, 10 / portTICK_PERIOD_MS) == pdPASS) - { - if ((millis() - timer) >= connectionAttemptTimeout) - { - retVal = true; - } - xSemaphoreGive(serverSemaphore); - } - return retVal; - } -} NTRIP_SERVER_DATA; - -#endif // COMPILE_NETWORK - typedef enum { ESPNOW_OFF = 0, @@ -912,7 +777,7 @@ struct Settings // GNSS UART uint16_t serialGNSSRxFullThreshold = 50; // RX FIFO full interrupt. Max of ~128. See pinUART2Task(). - int uartReceiveBufferSize = 1024 * 4; // This buffer is filled automatically as the UART receives characters. EVK needs 4K + int uartReceiveBufferSize = 1024 * 2; // This buffer is filled automatically as the UART receives characters // Hardware bool enableExternalHardwareEventLogging = false; // Log when INT/TM2 pin goes low @@ -945,6 +810,8 @@ struct Settings // Network layer bool debugNetworkLayer = false; // Enable debugging of the network layer bool printNetworkStatus = true; // Print network status (delays, failovers, IP address) + // networkClient _timeout in ms (lib default is 3000). This limits write glitches to about 3.4s + uint32_t networkClientWriteTimeout_ms = 250; // NTP bool debugNtp = false; @@ -1613,6 +1480,7 @@ const RTK_Settings_Entry rtkSettingsEntries[] = // Network layer { 0, 0, 0, 1, 1, 1, 1, 1, 1, ALL, 1, _bool, 0, & settings.debugNetworkLayer, "debugNetworkLayer", nullptr, }, { 0, 0, 0, 1, 1, 1, 1, 1, 1, ALL, 1, _bool, 0, & settings.printNetworkStatus, "printNetworkStatus", nullptr, }, + { 0, 0, 0, 1, 1, 1, 1, 1, 1, ALL, 1, _uint32_t, 0, & settings.networkClientWriteTimeout_ms, "networkClientWriteTimeout", nullptr, }, // F // a diff --git a/Firmware/Tools/Dinger.py b/Firmware/Tools/Dinger.py new file mode 100644 index 000000000..311b9303e --- /dev/null +++ b/Firmware/Tools/Dinger.py @@ -0,0 +1,51 @@ +import argparse +import multiprocessing +import serial +from datetime import datetime + +def dinger(port, baud, find, printLines): + ser = serial.Serial(port, baud) + buffer = list(find) + for i in range(len(find)): + buffer[i] = ' ' + while True: + for i in range(1, len(find)): + buffer[i-1] = buffer[i] + buffer[len(find) - 1] = ser.read() + s = '' + for c in buffer: + s += chr(ord(c)) + if s == find: + print('\a') # Ding + print("Ding! " + str(find) + " found at " + datetime.now().isoformat()) + if (printLines > 0): + for i in range (printLines): + print(str(ser.readline())[2:-1]) + +if __name__ == "__main__": + + parser = argparse.ArgumentParser( + description='Dinger') + + parser.add_argument('-port', type=str, default="COM1", + help='COM port') + + parser.add_argument('-baud', type=int, default=115200, + help='Baud rate') + + parser.add_argument('-find', type=str, default="# => ", + help='Ding on this') + + parser.add_argument('-print', type=int, default=1, + help='Print following lines') + + args = parser.parse_args() + + proc = multiprocessing.Process(target = dinger, args = (args.port, args.baud, args.find, args.print)) + proc.start() + + try: + while True: + pass + except KeyboardInterrupt: + proc.terminate() diff --git a/Firmware/Tools/NTRIP_Sink.py b/Firmware/Tools/NTRIP_Sink.py new file mode 100644 index 000000000..5e489833c --- /dev/null +++ b/Firmware/Tools/NTRIP_Sink.py @@ -0,0 +1,60 @@ +import socket +import argparse +import multiprocessing +import time + +def client(server, port): + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock.bind((server, port)) + sock.listen() + totalBytes = 0 + lastBytes = 0 + startTime = time.time() + while True: + print("Waiting for new connection") + conn, addr = sock.accept() + print("Connection from " + str(addr)) + print("Sending ICY 200 OK") + conn.send(b"ICY 200 OK") + conn.settimeout(5.0) + keepGoing = True + while keepGoing: + try: + payload = conn.recv(1024) + except TimeoutError: + keepGoing = False + totalBytes += len(payload) + if time.time() - startTime > 5: + startTime = time.time() + rate = int((totalBytes - lastBytes) / 5) + lastBytes = totalBytes + print("Total bytes: " + str(totalBytes) + " (" + str(rate) + " Bps)") + conn.close() + print() + +if __name__ == "__main__": + + parser = argparse.ArgumentParser( + description='TCP Server') + + parser.add_argument('-server', type=str, default="", + help='Host Name or IP Address (e.g. 127.0.0.1 localhost)') + + parser.add_argument('-port', type=int, default=2101, + help='TCP Port Number') + + args = parser.parse_args() + + if (args.server != ""): + print("Listening on " + args.server + ":" + str(args.port)) + else: + print("Listening on port " + str(args.port)) + + proc = multiprocessing.Process(target = client, args = (args.server, args.port)) + proc.start() + + try: + while True: + pass + except KeyboardInterrupt: + proc.terminate()