diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/MTS_IO_CellularRadio.cpp | 35 | ||||
| -rw-r--r-- | src/MTS_IO_ICellularRadio.cpp | 2 | ||||
| -rw-r--r-- | src/MTS_IO_QuectelRadio.cpp | 286 | 
3 files changed, 322 insertions, 1 deletions
| diff --git a/src/MTS_IO_CellularRadio.cpp b/src/MTS_IO_CellularRadio.cpp index 50fdf5c..26d9f43 100644 --- a/src/MTS_IO_CellularRadio.cpp +++ b/src/MTS_IO_CellularRadio.cpp @@ -1008,6 +1008,24 @@ ICellularRadio::CODE CellularRadio::updateFumo(const Json::Value&, UpdateCb&) {      return NOT_APPLICABLE;  } +ICellularRadio::CODE CellularRadio::uploadDeltaFirmwareFile(int, ICellularRadio::UpdateCb&) { +    printTrace("%s| Upload Delta Firmware Upgrade File: not applicable", m_sName.c_str()); + +    return NOT_APPLICABLE; +} + +ICellularRadio::CODE CellularRadio::removeDeltaFirmwareFile() { +    printTrace("%s| Remove Delta Firmware Upgrade File: not applicable", m_sName.c_str()); + +    return NOT_APPLICABLE; +} + +ICellularRadio::CODE CellularRadio::applyDeltaFirmwareFile(ICellularRadio::UpdateCb&) { +    printTrace("%s| Apply Delta Firmware Upgrade File: not applicable", m_sName.c_str()); + +    return NOT_APPLICABLE; +} +  ICellularRadio::CODE CellularRadio::resetHfa(const Json::Value&, UpdateCb&) {      printTrace("%s| HFA Reset", m_sName.c_str()); @@ -1054,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 c9bed33..b84fa3f 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 5024f54..a8986f4 100644 --- a/src/MTS_IO_QuectelRadio.cpp +++ b/src/MTS_IO_QuectelRadio.cpp @@ -26,8 +26,17 @@  #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; +  QuectelRadio::QuectelRadio(const std::string& sName, const std::string& sRadioPort)  : CellularRadio (sName, sRadioPort)  { @@ -438,6 +447,46 @@ ICellularRadio::CODE QuectelRadio::setMdn(const Json::Value& jArgs) {      return NOT_APPLICABLE;  } +ICellularRadio::CODE QuectelRadio::uploadDeltaFirmwareFile(int fd, ICellularRadio::UpdateCb& stepCb) { +    CODE rc = FAILURE; +    bool bIsFilePresent = false; + +    do { +        rc = checkFile(bIsFilePresent, VALUE_MTS_DELTA_NAME); +        if (rc != SUCCESS) { +            printError("Failed to check if the delta file was already uploaded."); +            break; +        } + +        if (bIsFilePresent) { +            rc = removeDeltaFirmwareFile(); +        } + +        if (rc != SUCCESS) { +            printError("Failed to remove the previous delta file."); +            break; +        } + +        rc = uploadFile(fd, VALUE_MTS_DELTA_NAME, stepCb); +        if (rc != SUCCESS) { +            printError("Failed to upload the delta file."); +            break; +        } + +    } while (false); + +    return rc; +} + +ICellularRadio::CODE QuectelRadio::removeDeltaFirmwareFile() { +    printTrace("Removing the delta upgrade file: %s", VALUE_MTS_DELTA_NAME.c_str()); +    return removeFile(VALUE_MTS_DELTA_NAME); +} + +ICellularRadio::CODE QuectelRadio::applyDeltaFirmwareFile(ICellularRadio::UpdateCb& stepCb) { +    return ERROR;  // not implemented +} +  ICellularRadio::CODE QuectelRadio::getServiceDomain(ICellularRadio::SERVICEDOMAIN& sd) {      printTrace("%s| Get Service Domain", getName().c_str()); @@ -602,3 +651,240 @@ ICellularRadio::CODE QuectelRadio::setCellularMode(CELLULAR_MODES networks) {      }      return SUCCESS;  } + +ICellularRadio::CODE QuectelRadio::uploadFile(int fd, const std::string& sTargetFilename, ICellularRadio::UpdateCb& stepCb) { +    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) { +    printTrace("Removing file [%s] from the radio memory", sTargetFilename.c_str()); + +    const int dTimeout = 1000; //ms +    const std::string sCmd = "AT+QFDEL=\"" + sTargetFilename + "\""; + +    std::string sResult = sendCommand(sCmd, ICellularRadio::DEFAULT_BAIL_STRINGS, dTimeout); +    if (sResult.find(ICellularRadio::RSP_OK) == std::string::npos) { +        printError("Failed to remove file [%s]: [%s]", sTargetFilename.c_str(), sResult.c_str()); +        return FAILURE; +    } + +    printTrace("File [%s] removed", sTargetFilename.c_str()); +    return SUCCESS; +} + +ICellularRadio::CODE QuectelRadio::checkFile(bool& bIsFilePresent, const std::string& sTargetFilename) { +    printTrace("Checking status of the [%s] file", sTargetFilename.c_str()); + +    const int dTimeout = 1000; //ms +    const std::string sCmd = "AT+QFLST";  // list all files in the UFS memory + +    std::string sResult = sendCommand(sCmd, ICellularRadio::DEFAULT_BAIL_STRINGS, dTimeout); +    if (sResult.rfind(ICellularRadio::RSP_OK) == std::string::npos) { +        printError("Unable to list files from the radio memory: [%s]", sResult.c_str()); +        return FAILURE; +    } + +    const std::string sExpected = "+QFLST: \"UFS:" + sTargetFilename + "\""; +    bIsFilePresent = (sResult.find(sExpected) != std::string::npos); + +    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); +} | 
