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 | |
| 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')
| -rw-r--r-- | src/MTS_IO_CellularRadio.cpp | 8 | ||||
| -rw-r--r-- | src/MTS_IO_ICellularRadio.cpp | 22 | ||||
| -rw-r--r-- | src/MTS_IO_QuectelRadio.cpp | 152 | 
3 files changed, 147 insertions, 35 deletions
| 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<std::string>& 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 9dbfad1..73737c7 100644 --- a/src/MTS_IO_ICellularRadio.cpp +++ b/src/MTS_IO_ICellularRadio.cpp @@ -376,6 +376,28 @@ std::string MTS::IO::ICellularRadio::sendCommand(MTS::AutoPtr<MTS::IO::Connectio          return "";      } +    return waitResponse(apIo, isNeedMoreData, timeoutMillis); +} + +std::string MTS::IO::ICellularRadio::waitResponse(MTS::AutoPtr<MTS::IO::Connection>& apIo, const std::vector<std::string>& 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<MTS::IO::Connection>& apIo, MTS::IO::ICellularRadio::IsNeedMoreData& isNeedMoreData, int32_t timeoutMillis) {      bool done = false;      const uint32_t capacity = 1024;      char buffer[capacity]; 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) { | 
