From 64d8e39ac05a89acfb9173fad05d0aab948032c6 Mon Sep 17 00:00:00 2001 From: Serhii Kostiuk Date: Tue, 4 Aug 2020 11:15:30 +0300 Subject: Quectel EG25-G Delta Radio Firmware Upgrade support - libmts-io implementation Delta Radio Firmware Upgrade implementation uses file management commands for Quectel radios. Radio behaviour for +QFLST output on EG25 radios is different that on EG95: - EG95 radios list all files on UFS _with_ "UFS:" prefix; - EG25 radios list all files on UFS _without_ "UFS:" prefix. This commit allows to handle both formats of +QFLST output to check file presense. --- src/MTS_IO_QuectelRadio.cpp | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/MTS_IO_QuectelRadio.cpp b/src/MTS_IO_QuectelRadio.cpp index 8656973..601674b 100644 --- a/src/MTS_IO_QuectelRadio.cpp +++ b/src/MTS_IO_QuectelRadio.cpp @@ -968,9 +968,15 @@ ICellularRadio::CODE QuectelRadio::checkFile(bool& bIsFilePresent, const std::st return FAILURE; } - const std::string sExpected = "+QFLST: \"UFS:" + sTargetFilename + "\""; - bIsFilePresent = (sResult.find(sExpected) != std::string::npos); + // UFS files in +QFLST output may or may not have "UFS:" prefix + const std::string sExpectedFull = "+QFLST: \"UFS:" + sTargetFilename + "\""; + const std::string sExpectedAlt = "+QFLST: \"" + sTargetFilename + "\""; + // File is present if file name found with or without "UFS:" prefix in QFLST output + bool bIsPresentFull = (sResult.find(sExpectedFull) != std::string::npos); + bool bIsPresentAlt = (sResult.find(sExpectedAlt) != std::string::npos); + + bIsFilePresent = (bIsPresentFull || bIsPresentAlt); return SUCCESS; } -- cgit v1.2.3 From 1afb3c7bb338eba0410f85be8e5eaf192a7c4857 Mon Sep 17 00:00:00 2001 From: Serhii Kostiuk Date: Tue, 4 Aug 2020 10:20:54 +0300 Subject: Quectel EG25-G Delta Radio Firmware Upgrade support - libmts-io implementation During testing with L4G1 device I discovered some inconsistencies in behaviour between EG25-G device and EG95 devices (EG95-NA and EG95-E). EG25-G device that I have on hands does not allow to perform downgrades using delta images. When there is an attempt to apply a downgrade delta image it behaves as the following: - radio prints `OK` - radio prints `+QIND: "FOTA",502` - radio is not rebooted While EG95 radios always reboot at least once to apply the firmware image. Even if it is not valid. Also I noticed that detach from the serial bus may take more than 10 seconds in some rare cases. Thus we need to wait not a fixed amount of time but until the radio actually detaches from the bus. This commit attempts to address the findings described above. --- include/mts/MTS_IO_QuectelRadio.h | 3 +++ src/MTS_IO_QuectelRadio.cpp | 49 +++++++++++++++++++++++++++++++++++---- 2 files changed, 48 insertions(+), 4 deletions(-) diff --git a/include/mts/MTS_IO_QuectelRadio.h b/include/mts/MTS_IO_QuectelRadio.h index 58e3d25..0f24db8 100644 --- a/include/mts/MTS_IO_QuectelRadio.h +++ b/include/mts/MTS_IO_QuectelRadio.h @@ -86,6 +86,9 @@ namespace MTS { CODE fumoWaitUpgradeFinished(UpdateCb& stepCb); CODE fumoWaitNewFirmware(UpdateCb& stepCb); + /// Parse error code in early FUMO URC result received before the first attempt to flash the firmware + std::string getFumoEarlyErrorCode(const std::string& sRadioInput); + /// Get value from container by its index, use default value if not found. Non-template version. const std::string& getByIndex(const std::vector& vector, size_t index, const std::string& defaultValue) { if (index >= vector.size()) { diff --git a/src/MTS_IO_QuectelRadio.cpp b/src/MTS_IO_QuectelRadio.cpp index 601674b..7d205e7 100644 --- a/src/MTS_IO_QuectelRadio.cpp +++ b/src/MTS_IO_QuectelRadio.cpp @@ -608,8 +608,12 @@ ICellularRadio::CODE QuectelRadio::fumoLocalCleanup() { } ICellularRadio::CODE QuectelRadio::fumoLocalApply(ICellularRadio::UpdateCb& stepCb) { + const std::string sFotaUrcPrefix = "+QIND: \"FOTA\""; // prefix for URC notification messages + const std::vector vFotaBailStrings{ sFotaUrcPrefix }; + ICellularRadio::CODE rc; std::string sCmd; + std::string sResponse; rc = getVendorFirmware(m_sQuectelFirmware); if (rc != SUCCESS) { @@ -631,8 +635,8 @@ ICellularRadio::CODE QuectelRadio::fumoLocalApply(ICellularRadio::UpdateCb& step return rc; } - const uint32_t duDetachTimeout = 10000; // wait for 10 seconds for the radio to detach - const uint32_t duAttachTimeout = 30000; // wait for 30 seconds for the radio to attach + const uint32_t duDetachTimeout = 15000; // wait up to 15 seconds for the radio to detach + const uint32_t duAttachTimeout = 30000; // wait up to 30 seconds for the radio to attach const int dMaxAttempts = 5; // the radio makes 5 attempts to update the firmware for (int i = 0; i < dMaxAttempts; i++) { @@ -640,8 +644,22 @@ ICellularRadio::CODE QuectelRadio::fumoLocalApply(ICellularRadio::UpdateCb& step printInfo("Waiting for the radio to enter recovery mode"); callNextStep(stepCb, "FUMO Info: waiting for the radio to enter recovery mode"); - // Wait for the radio to detach from the USB bus - MTS::Thread::sleep(duDetachTimeout); + // Wait for new FOTA responses. Exits preliminary if radio detached. + sResponse = sendCommand("", vFotaBailStrings, duDetachTimeout, 0x00); + printTrace("Radio response: [%s]", sResponse.c_str()); + + if (i == 0 && sResponse.find(sFotaUrcPrefix) != std::string::npos) { + // FOTA responce code received before radio detach, image can't be applied. + // EG25-G radio prints a message in +QIND: "FOTA",ERR_CODE format in this case. + std::string sErrorCode = getFumoEarlyErrorCode(sResponse); + + printError("Preliminary termination of FUMO procedure: [%s]", sErrorCode.c_str()); + callNextStep(stepCb, "FUMO Error: radio returned error code " + sErrorCode); + + // We got a response from the radio but FOTA failed + rc = FAILURE; + break; + } // It's now detached. Try to reconnect if (!resetConnection(duAttachTimeout)) { @@ -1116,6 +1134,29 @@ ICellularRadio::CODE QuectelRadio::fumoWaitNewFirmware(ICellularRadio::UpdateCb& return rc; } +std::string QuectelRadio::getFumoEarlyErrorCode(const std::string& sRadioInput) { + const std::string sFotaPrefix = "+QIND: \"FOTA\","; + const char cLineEnd = ICellularRadio::CR; + + // sRadioInput may contain several lines. We need to find the line with '+QIND: "FOTA",' in it + auto pLineStart = sRadioInput.find(sFotaPrefix); + + if (pLineStart == std::string::npos) { + // FOTA line not found at all + return "-1"; + } + + // Parse the error code + auto pErrorStart = pLineStart + sFotaPrefix.length(); + auto pLineEnd = sRadioInput.find(cLineEnd, pErrorStart); + + // Filter the error code + std::string sSubString = sRadioInput.substr(pErrorStart, pLineEnd - pErrorStart); + std::string sResult = MTS::Text::trim(sSubString); + + return sResult; +} + ICellularRadio::CODE QuectelRadio::startFileUpload(const std::string& sTargetFilename, size_t nBytes) { const std::vector vBailStrings{ ICellularRadio::RSP_CONNECT, ICellularRadio::RSP_ERROR }; const int dTimeout = 1000; //ms -- cgit v1.2.3 From 51a4db87617b4dc7d9276e31298d0a9d44ecb69e Mon Sep 17 00:00:00 2001 From: Serhii Kostiuk Date: Tue, 4 Aug 2020 12:21:10 +0300 Subject: Quectel EG25-G Delta Radio Firmware Upgrade support - libmts-io implementation CellularRadio::resetConnection implementations disables serial echo on connection reset. Serial echo is disabled using ATE0 command, radio may handle it and return an OK response. During testing I discovered that EG25 radio does not return an OK response in 100ms (default timeout time) and this OK response may conflict with successive AT commands. This commit adds a simple "AT" command to be executed before getVendorFirmware to "eat" all the data accumulated in the buffer. --- src/MTS_IO_QuectelRadio.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/MTS_IO_QuectelRadio.cpp b/src/MTS_IO_QuectelRadio.cpp index 7d205e7..96a4980 100644 --- a/src/MTS_IO_QuectelRadio.cpp +++ b/src/MTS_IO_QuectelRadio.cpp @@ -1104,7 +1104,9 @@ ICellularRadio::CODE QuectelRadio::fumoWaitNewFirmware(ICellularRadio::UpdateCb& MTS::Thread::sleep(10000); - if (getVendorFirmware(sQuectelFirmware) != SUCCESS) { + // sendBasicCommand "eats" and clears all the extra data present in the buffer, + // both commands shall succeed + if (sendBasicCommand("AT") != SUCCESS || getVendorFirmware(sQuectelFirmware) != SUCCESS) { // The radio is probably unavailable resetConnection(100); continue; -- cgit v1.2.3 From c7f63212d7e6596975835f23150ef20aea3b7b96 Mon Sep 17 00:00:00 2001 From: Serhii Kostiuk Date: Wed, 5 Aug 2020 14:10:25 +0300 Subject: Quectel EG25-G Delta Radio Firmware Upgrade support - libmts-io implementation Increase the timeout for +QFUPL response to 10 seconds. 5 seconds is the default timeout for the radio to wait for new data on its serial interface. +QFUPL response timeout was increased to 10 seconds to debug cases when hardware flow control fails for some reason and the radio does not receive all the data sent to it from the device. --- src/MTS_IO_QuectelRadio.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/MTS_IO_QuectelRadio.cpp b/src/MTS_IO_QuectelRadio.cpp index 96a4980..942c0f7 100644 --- a/src/MTS_IO_QuectelRadio.cpp +++ b/src/MTS_IO_QuectelRadio.cpp @@ -938,7 +938,7 @@ ICellularRadio::CODE QuectelRadio::uploadFile(int fd, const std::string& sTarget sExpectedResult += MTS::Text::toLowerCase(MTS::Text::formatHex(dChecksum)); // "send" empty string to read acknoledge string - std::string sResult = sendCommand("", DEFAULT_BAIL_STRINGS, 3000, 0x00); + std::string sResult = sendCommand("", DEFAULT_BAIL_STRINGS, 10000, 0x00); if (sResult.find(sExpectedResult) != std::string::npos) { printDebug("Radio returned: [%s]", sResult.c_str()); -- cgit v1.2.3 From 96445eaa166dd8f4ba99014f5eea2c1146b9db80 Mon Sep 17 00:00:00 2001 From: Serhii Kostiuk Date: Thu, 6 Aug 2020 13:11:42 +0300 Subject: Quectel EG25-G Delta Radio Firmware Upgrade support - libmts-io implementation Refactored MTS::IO::ICellularRadio::sendCommand. Moved response waiting code to the MTS::IO::ICellularRadio::waitResponse function. **Motivation** In many places in the modern libmts-io implementation there are cases when we need to wait for some response from the radio without executing or sending any commands. Such places may wait for some URC messages, acknowledge strings during data transmission and so on. One way to handle such cases is to use `sendCommand` with `cmd` argument set to an empty string. It generally works but according to POSIX: "If count is zero and fd refers to a file other than a regular file, the results are not specified." The other way to handle such cases is to use `isNeedMoreData` callback of `sendCommand` function to analyze all the data on the fly. But this approach may not work for data transfers when `sendCommand` and its std::string argument may not be used to store binary data with null characters within. This commit moves the "wait for the radio to answer" part to the separate function which allows reusablity of such a code. --- include/mts/MTS_IO_ICellularRadio.h | 6 ++++++ src/MTS_IO_ICellularRadio.cpp | 4 ++++ 2 files changed, 10 insertions(+) diff --git a/include/mts/MTS_IO_ICellularRadio.h b/include/mts/MTS_IO_ICellularRadio.h index 085c217..e4456cc 100644 --- a/include/mts/MTS_IO_ICellularRadio.h +++ b/include/mts/MTS_IO_ICellularRadio.h @@ -83,6 +83,12 @@ namespace MTS { const std::vector& vBail = DEFAULT_BAIL_STRINGS, int32_t timeoutMillis = 100, const char& ESC = CR); + + //! Wait for response from the radio without sending any data to it + static std::string waitResponse(MTS::AutoPtr& apIo, + IsNeedMoreData& isNeedMoreData, + int32_t timeoutMillis = 100); + static CODE test(MTS::AutoPtr& apIo, uint32_t timeoutSeconds = 30); static std::string extractModelFromResult(const std::string& sResult); static std::string getCodeAsString(CODE code); diff --git a/src/MTS_IO_ICellularRadio.cpp b/src/MTS_IO_ICellularRadio.cpp index 9dbfad1..69f731a 100644 --- a/src/MTS_IO_ICellularRadio.cpp +++ b/src/MTS_IO_ICellularRadio.cpp @@ -376,6 +376,10 @@ std::string MTS::IO::ICellularRadio::sendCommand(MTS::AutoPtr& apIo, MTS::IO::ICellularRadio::IsNeedMoreData& isNeedMoreData, int32_t timeoutMillis) { bool done = false; const uint32_t capacity = 1024; char buffer[capacity]; -- cgit v1.2.3 From e1855057708468bc5383d948be70da7179730231 Mon Sep 17 00:00:00 2001 From: Serhii Kostiuk Date: Thu, 6 Aug 2020 14:06:19 +0300 Subject: Quectel EG25-G Delta Radio Firmware Upgrade support - libmts-io implementation Added waitResponse overloads to the public interface of ICellularRadio. --- include/mts/MTS_IO_CellularRadio.h | 7 +++++++ include/mts/MTS_IO_ICellularRadio.h | 11 +++++++++++ src/MTS_IO_CellularRadio.cpp | 8 ++++++++ src/MTS_IO_ICellularRadio.cpp | 18 ++++++++++++++++++ 4 files changed, 44 insertions(+) diff --git a/include/mts/MTS_IO_CellularRadio.h b/include/mts/MTS_IO_CellularRadio.h index ab6e00a..2b03d8f 100644 --- a/include/mts/MTS_IO_CellularRadio.h +++ b/include/mts/MTS_IO_CellularRadio.h @@ -126,6 +126,13 @@ namespace MTS { int32_t timeoutMillis = 100, const char& ESC = ICellularRadio::CR) override; + //! Wait for response from the radio without sending any data to it + virtual std::string waitResponse(const std::vector& vBail = DEFAULT_BAIL_STRINGS, + int32_t timeoutMillis = 100) override; + + //! Wait for response from the radio without sending any data to it + virtual std::string waitResponse(IsNeedMoreData& isNeedMoreData, + int32_t timeoutMillis = 100) override; protected: diff --git a/include/mts/MTS_IO_ICellularRadio.h b/include/mts/MTS_IO_ICellularRadio.h index e4456cc..61ffd20 100644 --- a/include/mts/MTS_IO_ICellularRadio.h +++ b/include/mts/MTS_IO_ICellularRadio.h @@ -88,6 +88,9 @@ namespace MTS { static std::string waitResponse(MTS::AutoPtr& apIo, IsNeedMoreData& isNeedMoreData, int32_t timeoutMillis = 100); + static std::string waitResponse(MTS::AutoPtr& apIo, + const std::vector& vBail = DEFAULT_BAIL_STRINGS, + int32_t timeoutMillis = 100); static CODE test(MTS::AutoPtr& apIo, uint32_t timeoutSeconds = 30); static std::string extractModelFromResult(const std::string& sResult); @@ -551,6 +554,14 @@ namespace MTS { int32_t timeoutMillis = 100, const char& ESC = CR) = 0; + //! Wait for response from the radio without sending any data to it + virtual std::string waitResponse(const std::vector& vBail = DEFAULT_BAIL_STRINGS, + int32_t timeoutMillis = 100) = 0; + + //! Wait for response from the radio without sending any data to it + virtual std::string waitResponse(IsNeedMoreData& isNeedMoreData, + int32_t timeoutMillis = 100) = 0; + }; } } diff --git a/src/MTS_IO_CellularRadio.cpp b/src/MTS_IO_CellularRadio.cpp index 49a2b32..ce95929 100644 --- a/src/MTS_IO_CellularRadio.cpp +++ b/src/MTS_IO_CellularRadio.cpp @@ -1133,6 +1133,14 @@ std::string CellularRadio::sendCommand(const std::string& sCmd, MTS::IO::Cellula return ICellularRadio::sendCommand(m_apIo, sCmd, isNeedMoreData, timeoutMillis, ESC); } +std::string CellularRadio::waitResponse(const std::vector& vBail, int32_t timeoutMillis) { + return ICellularRadio::waitResponse(m_apIo, vBail, timeoutMillis); +} + +std::string CellularRadio::waitResponse(ICellularRadio::IsNeedMoreData& isNeedMoreData, int32_t timeoutMillis) { + return ICellularRadio::waitResponse(m_apIo, isNeedMoreData, timeoutMillis); +} + ICellularRadio::CODE CellularRadio::sendData(const char* pData, size_t nBytes) { if(m_apIo.isNull()) { printError("RADIO| IO is not set in sendData"); diff --git a/src/MTS_IO_ICellularRadio.cpp b/src/MTS_IO_ICellularRadio.cpp index 69f731a..73737c7 100644 --- a/src/MTS_IO_ICellularRadio.cpp +++ b/src/MTS_IO_ICellularRadio.cpp @@ -379,6 +379,24 @@ std::string MTS::IO::ICellularRadio::sendCommand(MTS::AutoPtr& apIo, const std::vector& vBail, int32_t timeoutMillis) { + IsNeedMoreData isNeedMoreData = [&vBail](const std::string&, const std::string& allData)->bool { + for(size_t i = 0; i < vBail.size(); i++) { + const std::string& sBail = vBail[i]; + if(sBail.size() > 0) { + if(allData.find(sBail) != std::string::npos) { + //Return when bail string is found + printTrace("RADIO| Found bail string [%s]", sBail.c_str()); + return false; + } + } + } + return true; + }; + + return waitResponse(apIo, isNeedMoreData, timeoutMillis); +} + std::string MTS::IO::ICellularRadio::waitResponse(MTS::AutoPtr& apIo, MTS::IO::ICellularRadio::IsNeedMoreData& isNeedMoreData, int32_t timeoutMillis) { bool done = false; const uint32_t capacity = 1024; -- cgit v1.2.3 From c1a58778eecd0115d746afbca1079683b244b672 Mon Sep 17 00:00:00 2001 From: Serhii Kostiuk Date: Thu, 6 Aug 2020 14:47:49 +0300 Subject: Quectel EG25-G Delta Radio Firmware Upgrade support - libmts-io implementation During testing I discrovered that EG25-G radio may lose some data during transmission over Serial AT interface or just freezes and stops responding over Serial AT interface. When ACK mode is not used, the radio may either return an error: ``` Expected: [+QFUPL: 24312545,fa6b], Actual: [+QFUPL: 17124608,b907 +CME ERROR: 409 // Fail to write the file ] ``` Or it may just freeze on modem_at1 interface and stop responding to AT commands: ``` 8:0:34:32|TRACE|RADIO| Sending command [AT] 8:0:34:133|DEBUG|RESULT: 8:0:34:133|DEBUG|Shutting Down ``` This commit implements an alternative, ACK mode for data transmission to prevent data losses. Data is sent in chunks and the device waits for ACK string from the radio for each chunk. --- include/mts/MTS_IO_QuectelRadio.h | 2 +- src/MTS_IO_QuectelRadio.cpp | 71 ++++++++++++++++++++++++++------------- 2 files changed, 49 insertions(+), 24 deletions(-) diff --git a/include/mts/MTS_IO_QuectelRadio.h b/include/mts/MTS_IO_QuectelRadio.h index 0f24db8..5199b40 100644 --- a/include/mts/MTS_IO_QuectelRadio.h +++ b/include/mts/MTS_IO_QuectelRadio.h @@ -73,7 +73,7 @@ namespace MTS { static const std::string VALUE_MTS_DELTA_NAME; static const std::string VALUE_MTS_DELTA_PATH; - CODE startFileUpload(const std::string& sTargetFilename, size_t nBytes); + CODE startFileUpload(const std::string& sTargetFilename, size_t nBytes, uint16_t uRxTimeout = 5, bool bAckEnabled = false); CODE abortFileUpload(); static inline void callNextStep(UpdateCb& stepCb, const char* csMessage); diff --git a/src/MTS_IO_QuectelRadio.cpp b/src/MTS_IO_QuectelRadio.cpp index 942c0f7..a41e6fc 100644 --- a/src/MTS_IO_QuectelRadio.cpp +++ b/src/MTS_IO_QuectelRadio.cpp @@ -871,6 +871,9 @@ ICellularRadio::CODE QuectelRadio::setCellularMode(CELLULAR_MODES networks) { } ICellularRadio::CODE QuectelRadio::uploadFile(int fd, const std::string& sTargetFilename, ICellularRadio::UpdateCb& stepCb) { + const uint16_t uFileTimeout = 2; // s + const int32_t iUploadResultTimeout = uFileTimeout * 2 * 1000; // ms + size_t dPayloadLength; size_t nChunks; CODE rc; @@ -888,7 +891,8 @@ ICellularRadio::CODE QuectelRadio::uploadFile(int fd, const std::string& sTarget printTrace("File size: %d bytes and %d chunks", dPayloadLength, nChunks); printTrace("Starting file upload..."); - rc = startFileUpload(sTargetFilename, dPayloadLength); + // Start file upload, set transmission timeouts and enable ACK mode + rc = startFileUpload(sTargetFilename, dPayloadLength, uFileTimeout, true); if (rc != SUCCESS) { return rc; } @@ -900,6 +904,20 @@ ICellularRadio::CODE QuectelRadio::uploadFile(int fd, const std::string& sTarget size_t nChunksPerCent = (nChunks / 100) + 1; size_t nFragmentLength = 0; std::array vBuffer; + std::string sResponse; + + // ACK waiter callback - wait for ACK and populate result code + IsNeedMoreData waitAck = [&rc](const std::string& iterationData, const std::string&/*allData*/)->bool { + if (iterationData.find('A') != std::string::npos) { + // No data needed + rc = SUCCESS; + return false; + } + + // We need more data + rc = NO_RESPONSE; + return true; + }; for (size_t iChunk = 1; iChunk < (nChunks + 1); iChunk++) { @@ -914,6 +932,14 @@ ICellularRadio::CODE QuectelRadio::uploadFile(int fd, const std::string& sTarget rc = sendData(vBuffer.data(), nFragmentLength); if (rc != SUCCESS) { + // failed to send data + break; + } + + // Wait for ACK for up to 1 second and populate rc variable + sResponse = waitResponse(waitAck, 1000); + if (rc != SUCCESS) { + // timeout or end of execution - check later break; } @@ -924,38 +950,28 @@ ICellularRadio::CODE QuectelRadio::uploadFile(int fd, const std::string& sTarget } - if (rc != SUCCESS) { - // cancel upload and terminate - callNextStep(stepCb, "FILE Error: Upload failed due to internal error"); - abortFileUpload(); - return rc; - } - printTrace("Waiting for acknoledge from the radio"); std::string sExpectedResult = "+QFUPL: "; sExpectedResult += MTS::Text::format(dPayloadLength); sExpectedResult += ","; sExpectedResult += MTS::Text::toLowerCase(MTS::Text::formatHex(dChecksum)); - // "send" empty string to read acknoledge string - std::string sResult = sendCommand("", DEFAULT_BAIL_STRINGS, 10000, 0x00); + // Wait for confirmation of successful upload completion + sResponse += waitResponse(DEFAULT_BAIL_STRINGS, iUploadResultTimeout); - if (sResult.find(sExpectedResult) != std::string::npos) { - printDebug("Radio returned: [%s]", sResult.c_str()); + if (sResponse.find(sExpectedResult) != std::string::npos) { + printDebug("Radio returned: [%s]", sResponse.c_str()); printTrace("Upload finished, checksum matched"); - } else { - printError("Upload failed: checksum mismatch. Expected: [%s], Actual: [%s]", sExpectedResult.c_str(), sResult.c_str()); - abortFileUpload(); - rc = FAILURE; - } - - if (rc == SUCCESS) { callNextStep(stepCb, "FILE Info: Upload finished successfully"); - } else { - callNextStep(stepCb, "FILE Error: Upload failed due to internal error"); + + return SUCCESS; } - return rc; + printError("Upload failed: checksum mismatch. Expected: [%s], Actual: [%s]", sExpectedResult.c_str(), sResponse.c_str()); + callNextStep(stepCb, "FILE Error: Upload failed due to internal error"); + + abortFileUpload(); + return FAILURE; } ICellularRadio::CODE QuectelRadio::removeFile(const std::string& sTargetFilename) { @@ -1159,15 +1175,24 @@ std::string QuectelRadio::getFumoEarlyErrorCode(const std::string& sRadioInput) return sResult; } -ICellularRadio::CODE QuectelRadio::startFileUpload(const std::string& sTargetFilename, size_t nBytes) { +ICellularRadio::CODE QuectelRadio::startFileUpload(const std::string& sTargetFilename, size_t nBytes, uint16_t uRxTimeout, bool bAckEnabled) { const std::vector vBailStrings{ ICellularRadio::RSP_CONNECT, ICellularRadio::RSP_ERROR }; const int dTimeout = 1000; //ms std::string sCommand, sResult; + // Format: AT+QFUPL=[,[,[,]] + sCommand = "AT+QFUPL=\""; sCommand += sTargetFilename; sCommand += "\","; sCommand += MTS::Text::format(nBytes); + sCommand += ","; + sCommand += MTS::Text::format(uRxTimeout); + + if (bAckEnabled) { + // ACK mode enabled + sCommand += ",1"; + } sResult = sendCommand(sCommand, vBailStrings, dTimeout); if (sResult.find(ICellularRadio::RSP_CONNECT) == std::string::npos) { -- cgit v1.2.3 From 5c529e115394ba3d04c7531d046776293e2bf06c Mon Sep 17 00:00:00 2001 From: Serhii Kostiuk Date: Fri, 7 Aug 2020 11:07:48 +0300 Subject: Quectel EG25-G Delta Radio Firmware Upgrade support - libmts-io implementation This commit fixes additional issue discrovered after implementation of ACK mode. When a single 1024 bytes long chunk of data is sent to the radio - the radio reports that it received nothing. Which is a lie according to Wireshark USB logs, data is actually sent over USB and looks like radio acknowledged it in USB packet. This issue is reproducible when chunk size is set to 1024 or 512 bytes. This issue is not reproducible with chunk size of 256 bytes. When 1025 or 2048 bytes are sent to the radio - all bytes are received just fine and we get an ACK string for each 1024 bytes of data. This commit allows to send 2048 bytes long chunks of data, wait for two ACK strings from the radio and finish transmission successfully. --- src/MTS_IO_QuectelRadio.cpp | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/src/MTS_IO_QuectelRadio.cpp b/src/MTS_IO_QuectelRadio.cpp index a41e6fc..67a8d71 100644 --- a/src/MTS_IO_QuectelRadio.cpp +++ b/src/MTS_IO_QuectelRadio.cpp @@ -31,7 +31,7 @@ using namespace MTS::IO; -const size_t QuectelRadio::FILE_CHUNK_SIZE = 1024; +const size_t QuectelRadio::FILE_CHUNK_SIZE = 2048; const std::string QuectelRadio::CMD_ABORT_UPLOAD = "+++"; // It is strongly recommended to use DOS 8.3 file name format for . @@ -874,6 +874,11 @@ ICellularRadio::CODE QuectelRadio::uploadFile(int fd, const std::string& sTarget const uint16_t uFileTimeout = 2; // s const int32_t iUploadResultTimeout = uFileTimeout * 2 * 1000; // ms + // Quectel radios return ACK string every 1024 bytes + const size_t nChunksPerAck = std::max(static_cast(1), (1024 / FILE_CHUNK_SIZE)); + const size_t nAcksPerChunk = std::max(static_cast(1), (FILE_CHUNK_SIZE / 1024)); + const std::string sAckString(nAcksPerChunk, 'A'); + size_t dPayloadLength; size_t nChunks; CODE rc; @@ -907,8 +912,8 @@ ICellularRadio::CODE QuectelRadio::uploadFile(int fd, const std::string& sTarget std::string sResponse; // ACK waiter callback - wait for ACK and populate result code - IsNeedMoreData waitAck = [&rc](const std::string& iterationData, const std::string&/*allData*/)->bool { - if (iterationData.find('A') != std::string::npos) { + IsNeedMoreData waitAck = [&rc, &sAckString](const std::string& /*iterationData*/, const std::string& allData)->bool { + if (allData.find(sAckString) != std::string::npos) { // No data needed rc = SUCCESS; return false; @@ -919,7 +924,7 @@ ICellularRadio::CODE QuectelRadio::uploadFile(int fd, const std::string& sTarget return true; }; - for (size_t iChunk = 1; iChunk < (nChunks + 1); iChunk++) { + for (size_t iChunk = 0; iChunk < nChunks; iChunk++) { rc = readChunk(fd, vBuffer.data(), vBuffer.size(), nFragmentLength); if (rc != SUCCESS) { @@ -936,11 +941,14 @@ ICellularRadio::CODE QuectelRadio::uploadFile(int fd, const std::string& sTarget break; } - // Wait for ACK for up to 1 second and populate rc variable - sResponse = waitResponse(waitAck, 1000); - if (rc != SUCCESS) { - // timeout or end of execution - check later - break; + // Every 1024 bytes + if ((iChunk) && (iChunk % nChunksPerAck == 0)) { + // Wait for ACK for up to 1 second and populate rc variable + sResponse = waitResponse(waitAck, 1000); + if (rc != SUCCESS) { + // timeout or end of execution - check later + break; + } } if (stepCb && ((iChunk % nChunksPerCent) == 0)) { -- cgit v1.2.3 From 68437a8e36e60eac503a1e7e6ed66d8acb74ad5d Mon Sep 17 00:00:00 2001 From: Serhii Kostiuk Date: Thu, 6 Aug 2020 17:49:42 +0300 Subject: Quectel EG25-G Delta Radio Firmware Upgrade support - libmts-io implementation Switched from `sendCommand` with empty command argument to `waitResponse` for URC analysis. Sending empty string to the serial interface is undefined behaviour according to the POSIX standart. More of it, we don't need to execute `write` on file descriptor to wait for URC messages. It was more like a hack than something that was actually required. This commit removes the hack and replaces it with a proper waiting for a response. --- src/MTS_IO_QuectelRadio.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/MTS_IO_QuectelRadio.cpp b/src/MTS_IO_QuectelRadio.cpp index 67a8d71..de36987 100644 --- a/src/MTS_IO_QuectelRadio.cpp +++ b/src/MTS_IO_QuectelRadio.cpp @@ -501,7 +501,7 @@ ICellularRadio::CODE QuectelRadio::startOmaDm(ICellularRadio::UpdateCb& stepCb) } // Wait for the "Start" response - std::string sResponse = sendCommand("", vOdmStartedStrings, iTimeoutStart, 0x00); + std::string sResponse = waitResponse(vOdmStartedStrings, iTimeoutStart); printDebug("%s| Radio returned: [%s]", getName().c_str(), sResponse.c_str()); // Received something unexpected or nothing at all? @@ -517,7 +517,7 @@ ICellularRadio::CODE QuectelRadio::startOmaDm(ICellularRadio::UpdateCb& stepCb) callNextStep(stepCb, "OMA DM Info: OMA DM started"); // Wait for the "End" or "Abnormal" response - sResponse = sendCommand("", vOdmFinishedStrings, iTimeoutEnd, 0x00); + sResponse = waitResponse(vOdmFinishedStrings, iTimeoutEnd); printDebug("%s| Radio returned: [%s]", getName().c_str(), sResponse.c_str()); // Received "Abnormal"? @@ -645,7 +645,7 @@ ICellularRadio::CODE QuectelRadio::fumoLocalApply(ICellularRadio::UpdateCb& step callNextStep(stepCb, "FUMO Info: waiting for the radio to enter recovery mode"); // Wait for new FOTA responses. Exits preliminary if radio detached. - sResponse = sendCommand("", vFotaBailStrings, duDetachTimeout, 0x00); + sResponse = waitResponse(vFotaBailStrings, duDetachTimeout); printTrace("Radio response: [%s]", sResponse.c_str()); if (i == 0 && sResponse.find(sFotaUrcPrefix) != std::string::npos) { @@ -1054,7 +1054,7 @@ ICellularRadio::CODE QuectelRadio::fumoWaitUpgradeFinished(ICellularRadio::Updat std::string sResponse; while (!bFinished) { // breaks on "FOTA","END" - sResponse = sendCommand("", vFotaBailStrings, duUrcTimeout, 0x00); + sResponse = waitResponse(vFotaBailStrings, duUrcTimeout); printTrace("Radio response: [%s]", sResponse.c_str()); if (sResponse.find(sFotaUrcPrefix) == std::string::npos) { -- cgit v1.2.3 From b32dbb2c5f12fbacf598edb812acab816068de00 Mon Sep 17 00:00:00 2001 From: Serhii Kostiuk Date: Fri, 7 Aug 2020 16:03:56 +0300 Subject: Quectel EG25-G Delta Radio Firmware Upgrade support - libmts-io implementation Small improvements in code comments. --- include/mts/MTS_IO_QuectelRadio.h | 2 +- src/MTS_IO_QuectelRadio.cpp | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/include/mts/MTS_IO_QuectelRadio.h b/include/mts/MTS_IO_QuectelRadio.h index 5199b40..9713f48 100644 --- a/include/mts/MTS_IO_QuectelRadio.h +++ b/include/mts/MTS_IO_QuectelRadio.h @@ -86,7 +86,7 @@ namespace MTS { CODE fumoWaitUpgradeFinished(UpdateCb& stepCb); CODE fumoWaitNewFirmware(UpdateCb& stepCb); - /// Parse error code in early FUMO URC result received before the first attempt to flash the firmware + /// Parse error code if +QIND: "FOTA" received before the first attempt to flash the firmware std::string getFumoEarlyErrorCode(const std::string& sRadioInput); /// Get value from container by its index, use default value if not found. Non-template version. diff --git a/src/MTS_IO_QuectelRadio.cpp b/src/MTS_IO_QuectelRadio.cpp index de36987..aeb06ab 100644 --- a/src/MTS_IO_QuectelRadio.cpp +++ b/src/MTS_IO_QuectelRadio.cpp @@ -1010,11 +1010,11 @@ ICellularRadio::CODE QuectelRadio::checkFile(bool& bIsFilePresent, const std::st return FAILURE; } - // UFS files in +QFLST output may or may not have "UFS:" prefix + // UFS files in +QFLST output may or may not have a "UFS:" prefix const std::string sExpectedFull = "+QFLST: \"UFS:" + sTargetFilename + "\""; const std::string sExpectedAlt = "+QFLST: \"" + sTargetFilename + "\""; - // File is present if file name found with or without "UFS:" prefix in QFLST output + // File is present if its name is present with or without "UFS:" prefix in QFLST output bool bIsPresentFull = (sResult.find(sExpectedFull) != std::string::npos); bool bIsPresentAlt = (sResult.find(sExpectedAlt) != std::string::npos); -- cgit v1.2.3