summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJeff Hatch <jhatch@multitech.com>2020-08-11 09:23:04 -0500
committerJeff Hatch <jhatch@multitech.com>2020-08-11 09:23:04 -0500
commitd154933f6a6220e7298260d5084f6516806406b6 (patch)
tree3e85c3ee1e6b9958dcc095e8e60770c30f73e43f
parent79925bf7853896834ace6788b9bc7d6edcd78301 (diff)
parentb32dbb2c5f12fbacf598edb812acab816068de00 (diff)
downloadlibmts-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
-rw-r--r--include/mts/MTS_IO_CellularRadio.h7
-rw-r--r--include/mts/MTS_IO_ICellularRadio.h17
-rw-r--r--include/mts/MTS_IO_QuectelRadio.h5
-rw-r--r--src/MTS_IO_CellularRadio.cpp8
-rw-r--r--src/MTS_IO_ICellularRadio.cpp22
-rw-r--r--src/MTS_IO_QuectelRadio.cpp152
6 files changed, 175 insertions, 36 deletions
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<std::string>& 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 085c217..61ffd20 100644
--- a/include/mts/MTS_IO_ICellularRadio.h
+++ b/include/mts/MTS_IO_ICellularRadio.h
@@ -83,6 +83,15 @@ namespace MTS {
const std::vector<std::string>& 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<MTS::IO::Connection>& apIo,
+ IsNeedMoreData& isNeedMoreData,
+ int32_t timeoutMillis = 100);
+ static std::string waitResponse(MTS::AutoPtr<MTS::IO::Connection>& apIo,
+ const std::vector<std::string>& vBail = DEFAULT_BAIL_STRINGS,
+ int32_t timeoutMillis = 100);
+
static CODE test(MTS::AutoPtr<MTS::IO::Connection>& apIo, uint32_t timeoutSeconds = 30);
static std::string extractModelFromResult(const std::string& sResult);
static std::string getCodeAsString(CODE code);
@@ -545,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<std::string>& 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/include/mts/MTS_IO_QuectelRadio.h b/include/mts/MTS_IO_QuectelRadio.h
index 58e3d25..9713f48 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);
@@ -86,6 +86,9 @@ namespace MTS {
CODE fumoWaitUpgradeFinished(UpdateCb& stepCb);
CODE fumoWaitNewFirmware(UpdateCb& stepCb);
+ /// 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.
const std::string& getByIndex(const std::vector<std::string>& vector, size_t index, const std::string& defaultValue) {
if (index >= vector.size()) {
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) {