diff options
author | Jeff Hatch <jhatch@multitech.com> | 2020-08-11 09:23:04 -0500 |
---|---|---|
committer | Jeff Hatch <jhatch@multitech.com> | 2020-08-11 09:23:04 -0500 |
commit | d154933f6a6220e7298260d5084f6516806406b6 (patch) | |
tree | 3e85c3ee1e6b9958dcc095e8e60770c30f73e43f /src/MTS_IO_QuectelRadio.cpp | |
parent | 79925bf7853896834ace6788b9bc7d6edcd78301 (diff) | |
parent | b32dbb2c5f12fbacf598edb812acab816068de00 (diff) | |
download | libmts-io-d154933f6a6220e7298260d5084f6516806406b6.tar.gz libmts-io-d154933f6a6220e7298260d5084f6516806406b6.tar.bz2 libmts-io-d154933f6a6220e7298260d5084f6516806406b6.zip |
Merge branch 'sk/l4g1-delta-fwu' into 'master'
Quectel EG25-G Delta Radio Firmware Upgrade support - libmts-io implementation
See merge request !33
Diffstat (limited to 'src/MTS_IO_QuectelRadio.cpp')
-rw-r--r-- | src/MTS_IO_QuectelRadio.cpp | 152 |
1 files changed, 117 insertions, 35 deletions
diff --git a/src/MTS_IO_QuectelRadio.cpp b/src/MTS_IO_QuectelRadio.cpp index 8656973..aeb06ab 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 <filename>. @@ -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"? @@ -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<std::string> 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 = waitResponse(vFotaBailStrings, duDetachTimeout); + 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)) { @@ -853,6 +871,14 @@ 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 + + // Quectel radios return ACK string every 1024 bytes + const size_t nChunksPerAck = std::max(static_cast<size_t>(1), (1024 / FILE_CHUNK_SIZE)); + const size_t nAcksPerChunk = std::max(static_cast<size_t>(1), (FILE_CHUNK_SIZE / 1024)); + const std::string sAckString(nAcksPerChunk, 'A'); + size_t dPayloadLength; size_t nChunks; CODE rc; @@ -870,7 +896,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; } @@ -882,8 +909,22 @@ ICellularRadio::CODE QuectelRadio::uploadFile(int fd, const std::string& sTarget size_t nChunksPerCent = (nChunks / 100) + 1; size_t nFragmentLength = 0; std::array<char, FILE_CHUNK_SIZE> vBuffer; + std::string sResponse; + + // ACK waiter callback - wait for ACK and populate result code + 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; + } - for (size_t iChunk = 1; iChunk < (nChunks + 1); iChunk++) { + // We need more data + rc = NO_RESPONSE; + return true; + }; + + for (size_t iChunk = 0; iChunk < nChunks; iChunk++) { rc = readChunk(fd, vBuffer.data(), vBuffer.size(), nFragmentLength); if (rc != SUCCESS) { @@ -896,9 +937,20 @@ ICellularRadio::CODE QuectelRadio::uploadFile(int fd, const std::string& sTarget rc = sendData(vBuffer.data(), nFragmentLength); if (rc != SUCCESS) { + // failed to send data 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)) { size_t dPercentsCompleted = iChunk / nChunksPerCent; callNextStep(stepCb, "FILE Info: Uploaded " + MTS::Text::format(dPercentsCompleted) + "%"); @@ -906,38 +958,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, 3000, 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) { @@ -968,9 +1010,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 a "UFS:" prefix + const std::string sExpectedFull = "+QFLST: \"UFS:" + sTargetFilename + "\""; + const std::string sExpectedAlt = "+QFLST: \"" + sTargetFilename + "\""; + + // 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); + bIsFilePresent = (bIsPresentFull || bIsPresentAlt); return SUCCESS; } @@ -1006,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) { @@ -1080,7 +1128,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; @@ -1110,15 +1160,47 @@ ICellularRadio::CODE QuectelRadio::fumoWaitNewFirmware(ICellularRadio::UpdateCb& return rc; } -ICellularRadio::CODE QuectelRadio::startFileUpload(const std::string& sTargetFilename, size_t nBytes) { +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, uint16_t uRxTimeout, bool bAckEnabled) { const std::vector<std::string> vBailStrings{ ICellularRadio::RSP_CONNECT, ICellularRadio::RSP_ERROR }; const int dTimeout = 1000; //ms std::string sCommand, sResult; + // Format: AT+QFUPL=<filename>[,<file_size>[,<timeout>[,<ackmode>]] + 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) { |