summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSerhii Kostiuk <serhii.o.kostiuk@globallogic.com>2020-05-29 21:02:15 +0300
committerSerhii Kostiuk <serhii.o.kostiuk@globallogic.com>2020-05-30 11:58:27 +0300
commit6e9ce61addd97809d5ea7b912332dd11a4bf7cee (patch)
treedc45ee1c7ce5cbdef3e2f0d9770c9562309072da
parent9f5a4f138b56a0a1b4e5764a69261aa4a4edaa71 (diff)
downloadlibmts-io-6e9ce61addd97809d5ea7b912332dd11a4bf7cee.tar.gz
libmts-io-6e9ce61addd97809d5ea7b912332dd11a4bf7cee.tar.bz2
libmts-io-6e9ce61addd97809d5ea7b912332dd11a4bf7cee.zip
Quectel Delta Radio Firmware Upgrade support - libmts-io implementation
Initial implementation of the delta firmware image upload for Quectel radios.
-rw-r--r--include/mts/MTS_IO_CellularRadio.h2
-rw-r--r--include/mts/MTS_IO_ICellularRadio.h1
-rw-r--r--include/mts/MTS_IO_QuectelRadio.h20
-rw-r--r--src/MTS_IO_CellularRadio.cpp17
-rw-r--r--src/MTS_IO_ICellularRadio.cpp2
-rw-r--r--src/MTS_IO_QuectelRadio.cpp206
6 files changed, 246 insertions, 2 deletions
diff --git a/include/mts/MTS_IO_CellularRadio.h b/include/mts/MTS_IO_CellularRadio.h
index 9fdb076..92df561 100644
--- a/include/mts/MTS_IO_CellularRadio.h
+++ b/include/mts/MTS_IO_CellularRadio.h
@@ -177,6 +177,8 @@ namespace MTS {
REGISTRATION parseRegResponse(std::string sResult);
CODE getRegistration(REGISTRATION& eRegistration, const std::string& sType);
+ virtual CODE sendData(const char* pData, size_t nBytes);
+
class RadioBandMap : public MTS::NonCopyable {
public:
RadioBandMap()
diff --git a/include/mts/MTS_IO_ICellularRadio.h b/include/mts/MTS_IO_ICellularRadio.h
index 5123ba1..7ad69c7 100644
--- a/include/mts/MTS_IO_ICellularRadio.h
+++ b/include/mts/MTS_IO_ICellularRadio.h
@@ -103,6 +103,7 @@ namespace MTS {
static const char *RSP_OK;
static const char *RSP_ERROR;
+ static const char *RSP_CONNECT;
static const char *VALUE_NOT_REGISTERED;
static const char *VALUE_REGISTERED;
diff --git a/include/mts/MTS_IO_QuectelRadio.h b/include/mts/MTS_IO_QuectelRadio.h
index 8aba437..9ccf50a 100644
--- a/include/mts/MTS_IO_QuectelRadio.h
+++ b/include/mts/MTS_IO_QuectelRadio.h
@@ -60,11 +60,31 @@ namespace MTS {
virtual CODE checkFile(bool& bFilePresent, const std::string& sTargetFilename);
private:
+ static const size_t FILE_CHUNK_SIZE;
+ static const std::string CMD_ABORT_UPLOAD;
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 abortFileUpload();
+
+ static uint16_t getQuectelChecksum(const void* data, size_t nBytes);
+ static inline void updateQuectelChecksum(uint16_t& iChecksum, uint16_t iNewFragment);
+ static inline uint16_t bytesToUint16(uint8_t high, uint8_t low);
+ static CODE getFileSize(int fd, size_t& nBytes, size_t& nFileChunks);
+ static CODE readChunk(int fd, char* pChunk, size_t dChunkSize, size_t& nReadBytes);
};
}
}
+void MTS::IO::QuectelRadio::updateQuectelChecksum(uint16_t& iChecksum, uint16_t iNewFragment) {
+ iChecksum = iChecksum ^ iNewFragment;
+}
+
+uint16_t MTS::IO::QuectelRadio::bytesToUint16(uint8_t high, uint8_t low) {
+ uint16_t comboHigh = static_cast<uint16_t>(high << 8); // explicit cast to prevent warnings
+ uint16_t comboLow = low;
+ return (comboHigh | comboLow);
+}
+
#endif /* MTS_IO_QUECTELRADIO_H_ */
diff --git a/src/MTS_IO_CellularRadio.cpp b/src/MTS_IO_CellularRadio.cpp
index d3bee11..26d9f43 100644
--- a/src/MTS_IO_CellularRadio.cpp
+++ b/src/MTS_IO_CellularRadio.cpp
@@ -1072,6 +1072,23 @@ std::string CellularRadio::sendCommand(const std::string& sCmd, MTS::IO::Cellula
return ICellularRadio::sendCommand(m_apIo, sCmd, isNeedMoreData, timeoutMillis, ESC);
}
+ICellularRadio::CODE CellularRadio::sendData(const char* pData, size_t nBytes) {
+ if(m_apIo.isNull()) {
+ printError("RADIO| IO is not set in sendData");
+ return ERROR;
+ }
+
+ int32_t iResult;
+ iResult = m_apIo->write(pData, nBytes);
+
+ if(iResult == -1) {
+ printError("RADIO| Failed to send data to radio");
+ return ERROR;
+ }
+
+ return SUCCESS;
+}
+
bool CellularRadio::splitAndAssign(const std::string& sLine, const std::string& sKey, Json::Value& jParent, const std::string& sJsonKey, Json::ValueType eType) {
std::vector<std::string> vParts = MTS::Text::split(sLine, ":", 2);
diff --git a/src/MTS_IO_ICellularRadio.cpp b/src/MTS_IO_ICellularRadio.cpp
index 53c8faa..2de52bb 100644
--- a/src/MTS_IO_ICellularRadio.cpp
+++ b/src/MTS_IO_ICellularRadio.cpp
@@ -11,7 +11,7 @@ const char MTS::IO::ICellularRadio::CTRL_Z = 0x1A;
const char *MTS::IO::ICellularRadio::RSP_ERROR = "ERROR";
const char *MTS::IO::ICellularRadio::RSP_OK = "OK";
-
+const char *MTS::IO::ICellularRadio::RSP_CONNECT = "CONNECT";
const char *MTS::IO::ICellularRadio::DEFAULT_RADIO_PORT = "/dev/modem_at1";
const char *MTS::IO::ICellularRadio::DEFAULT_RADIO_DIR = "/var/run/radio/";
diff --git a/src/MTS_IO_QuectelRadio.cpp b/src/MTS_IO_QuectelRadio.cpp
index d0ec310..8cd7194 100644
--- a/src/MTS_IO_QuectelRadio.cpp
+++ b/src/MTS_IO_QuectelRadio.cpp
@@ -24,8 +24,13 @@
#include <mts/MTS_Thread.h>
#include <mts/MTS_Text.h>
+#include <unistd.h>
+
using namespace MTS::IO;
+const size_t QuectelRadio::FILE_CHUNK_SIZE = 1024;
+const std::string QuectelRadio::CMD_ABORT_UPLOAD = "+++";
+
// It is strongly recommended to use DOS 8.3 file name format for <filename>.
const std::string QuectelRadio::VALUE_MTS_DELTA_NAME = "mtsdelta.zip";
const std::string QuectelRadio::VALUE_MTS_DELTA_PATH = "/data/ufs/" + QuectelRadio::VALUE_MTS_DELTA_NAME;
@@ -614,7 +619,87 @@ ICellularRadio::CODE QuectelRadio::convertToActiveBand(const std::string& sQuect
}
ICellularRadio::CODE QuectelRadio::uploadFile(int fd, const std::string& sTargetFilename, ICellularRadio::UpdateCb& stepCb) {
- return ERROR; // not implemented
+ size_t dPayloadLenght;
+ size_t nChunks;
+ CODE rc;
+
+ rc = getFileSize(fd, dPayloadLenght, nChunks);
+ if (rc != SUCCESS) {
+ return rc;
+ }
+ printTrace("File size: %d bytes and %d chunks", dPayloadLenght, nChunks);
+ printTrace("Starting file upload...");
+
+ rc = startFileUpload(sTargetFilename, dPayloadLenght);
+ if (rc != SUCCESS) {
+ return rc;
+ }
+
+ printTrace("File upload started.");
+ if (stepCb) {
+ stepCb(Json::Value("FILE info: Started file upload for " + sTargetFilename));
+ }
+
+ uint16_t dChecksum = 0;
+ size_t nChunksPerCent = (nChunks / 100) + 1;
+ size_t nFragmentLenght = 0;
+ std::array<char, FILE_CHUNK_SIZE> vBuffer;
+
+ for (size_t iChunk = 1; iChunk < (nChunks + 1); iChunk++) {
+
+ rc = readChunk(fd, vBuffer.data(), vBuffer.size(), nFragmentLenght);
+ if (rc != SUCCESS) {
+ break;
+ }
+
+ // we got our fragment, calculate checksum and flush the data
+ uint16_t dFragmentChecksum = getQuectelChecksum(vBuffer.data(), nFragmentLenght);
+ updateQuectelChecksum(dChecksum, dFragmentChecksum);
+
+ rc = sendData(vBuffer.data(), nFragmentLenght);
+ if (rc != SUCCESS) {
+ break;
+ }
+
+ if (stepCb && ((iChunk % nChunksPerCent) == 0)) {
+ size_t dPercentsCompleted = iChunk / nChunksPerCent;
+ stepCb(Json::Value("FILE info: Uploaded " + MTS::Text::format(dPercentsCompleted) + "%"));
+ }
+
+ }
+
+ if (rc != SUCCESS) {
+ // cancel upload and terminate
+ stepCb(Json::Value("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(dPayloadLenght);
+ 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);
+
+ if (sResult.find(sExpectedResult) != std::string::npos) {
+ printDebug("Radio returned: [%s]", sResult.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 (stepCb && rc == SUCCESS) {
+ stepCb(Json::Value("FILE info: Upload finished successfully"));
+ } else if (stepCb) {
+ stepCb(Json::Value("FILE error: Upload failed due to internal error"));
+ }
+
+ return rc;
}
ICellularRadio::CODE QuectelRadio::removeFile(const std::string& sTargetFilename) {
@@ -650,3 +735,122 @@ ICellularRadio::CODE QuectelRadio::checkFile(bool& bIsFilePresent, const std::st
return SUCCESS;
}
+
+uint16_t QuectelRadio::getQuectelChecksum(const void* data, size_t nBytes) {
+ auto castData = static_cast<const uint8_t*>(data);
+ uint16_t iChecksum = 0;
+
+ for (size_t i = 0; i < nBytes; i += 2) {
+ // If the number of the characters is odd, set the last character as the high 8 bit, and the low 8 bit as 0,
+ // and then use an XOR operator to calculate the checksum.
+ uint8_t high = castData[i];
+ uint8_t low = (i < nBytes) ? (castData[i+1]) : (0);
+
+ uint16_t iFragment = bytesToUint16(high, low);
+ updateQuectelChecksum(iChecksum, iFragment);
+ }
+
+ return iChecksum;
+}
+
+ICellularRadio::CODE QuectelRadio::getFileSize(int fd, size_t& nBytes, size_t& nChunks) {
+ CODE rc = FAILURE;
+
+ do {
+ ssize_t dScrollPos;
+ dScrollPos = lseek(fd, 0, SEEK_SET);
+ if (dScrollPos < 0) {
+ printError("Failed to seek to the start of the file: %d", errno);
+ break;
+ }
+
+ dScrollPos = lseek(fd, 0, SEEK_END);
+ if (dScrollPos < 0) {
+ printError("Failed to determine the file size: %d", errno);
+ break;
+ }
+
+ nBytes = static_cast<size_t>(dScrollPos);
+ nChunks = (nBytes + FILE_CHUNK_SIZE - 1) / FILE_CHUNK_SIZE; // round up
+
+ rc = SUCCESS;
+
+ } while (false);
+
+ lseek(fd, 0, SEEK_SET);
+ return rc;
+}
+
+ICellularRadio::CODE QuectelRadio::readChunk(int fd, char* pChunk, size_t dChunkSize, size_t& nReadBytes) {
+ size_t nUsedBuffer = 0;
+ CODE rc = FAILURE;
+
+ while (true) {
+
+ if (nUsedBuffer > dChunkSize) {
+ printError("Internal pointer error, abort upload: %d", nUsedBuffer);
+ rc = ERROR;
+ break;
+ }
+
+ if (nUsedBuffer == dChunkSize) {
+ // full chunk received
+ rc = SUCCESS;
+ nReadBytes = dChunkSize;
+ break;
+ }
+
+ char* pData = pChunk + nUsedBuffer;
+ size_t nFreeBuffer = dChunkSize - nUsedBuffer;
+
+ ssize_t dReadCount = read(fd, pData, nFreeBuffer);
+ if (dReadCount < 0) {
+ printError("Failed to read from the source file: %d", errno);
+ rc = ERROR;
+ break;
+ }
+
+ size_t duReadCount = static_cast<size_t>(dReadCount);
+ if (duReadCount == 0) {
+ // EOF. Return what was already read
+ nReadBytes = nUsedBuffer;
+ rc = SUCCESS;
+ break;
+ }
+
+ nUsedBuffer += duReadCount;
+
+ }
+
+ return rc;
+}
+
+ICellularRadio::CODE QuectelRadio::startFileUpload(const std::string& sTargetFilename, size_t nBytes) {
+ const std::vector<std::string> vBailStrings{ ICellularRadio::RSP_CONNECT, ICellularRadio::RSP_ERROR };
+ const int dTimeout = 1000; //ms
+ std::string sCommand, sResult;
+
+ sCommand = "AT+QFUPL=\"";
+ sCommand += sTargetFilename;
+ sCommand += "\",";
+ sCommand += MTS::Text::format(nBytes);
+
+ sResult = sendCommand(sCommand, vBailStrings, dTimeout);
+ if (sResult.find(ICellularRadio::RSP_CONNECT) == std::string::npos) {
+ printError("Radio is not ready to accept the file: [%s]", sResult.c_str());
+ return FAILURE;
+ }
+
+ return SUCCESS;
+}
+
+ICellularRadio::CODE QuectelRadio::abortFileUpload() {
+ /*
+ * To prevent the “+++” from being mistaken for data, the following sequence should be followed:
+ * 1) Do not input any character within 1s or longer before inputting “+++”.
+ * 2) Input “+++” within 1s, and no other characters can be inputted during the time.
+ * 3) Do not input any character within 1s after “+++” has been inputted.
+ */
+ sleep(1);
+ return sendBasicCommand(CMD_ABORT_UPLOAD, 2000);
+}