From 6e9ce61addd97809d5ea7b912332dd11a4bf7cee Mon Sep 17 00:00:00 2001 From: Serhii Kostiuk Date: Fri, 29 May 2020 21:02:15 +0300 Subject: Quectel Delta Radio Firmware Upgrade support - libmts-io implementation Initial implementation of the delta firmware image upload for Quectel radios. --- src/MTS_IO_CellularRadio.cpp | 17 ++++ src/MTS_IO_ICellularRadio.cpp | 2 +- src/MTS_IO_QuectelRadio.cpp | 206 +++++++++++++++++++++++++++++++++++++++++- 3 files changed, 223 insertions(+), 2 deletions(-) (limited to 'src') 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 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 #include +#include + 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 . 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 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(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(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(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 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); +} -- cgit v1.2.3