diff options
author | Jeff Hatch <jhatch@multitech.com> | 2020-07-22 08:25:08 -0500 |
---|---|---|
committer | Jeff Hatch <jhatch@multitech.com> | 2020-07-22 08:25:08 -0500 |
commit | d340135922da6eb881418824e470cbdba835b498 (patch) | |
tree | 90e162e6cf2c6a1d3b890a5483c9f77a000ced09 | |
parent | 343e662b6224cf03fea5ebfd419c7cf990528b53 (diff) | |
parent | beb3f36c5dcc70beed900859d6426dc74e4bccc8 (diff) | |
download | libmts-io-d340135922da6eb881418824e470cbdba835b498.tar.gz libmts-io-d340135922da6eb881418824e470cbdba835b498.tar.bz2 libmts-io-d340135922da6eb881418824e470cbdba835b498.zip |
Merge branch 'delta-radio-fwu' into 'master'
Delta Radio Firmware Upgrade support: LEU7 and L4E1
See merge request !30
-rw-r--r-- | include/mts/MTS_IO_CellularRadio.h | 11 | ||||
-rw-r--r-- | include/mts/MTS_IO_ICellularRadio.h | 68 | ||||
-rw-r--r-- | include/mts/MTS_IO_LE910C4EURadio.h | 1 | ||||
-rw-r--r-- | include/mts/MTS_IO_ME910Radio.h | 2 | ||||
-rw-r--r-- | include/mts/MTS_IO_QuectelRadio.h | 50 | ||||
-rw-r--r-- | include/mts/MTS_IO_TelitRadio.h | 49 | ||||
-rw-r--r-- | src/MTS_IO_CellularRadio.cpp | 133 | ||||
-rw-r--r-- | src/MTS_IO_ICellularRadio.cpp | 3 | ||||
-rw-r--r-- | src/MTS_IO_LE910C4EURadio.cpp | 4 | ||||
-rw-r--r-- | src/MTS_IO_ME910Radio.cpp | 32 | ||||
-rw-r--r-- | src/MTS_IO_QuectelRadio.cpp | 456 | ||||
-rw-r--r-- | src/MTS_IO_TelitRadio.cpp | 353 |
12 files changed, 1108 insertions, 54 deletions
diff --git a/include/mts/MTS_IO_CellularRadio.h b/include/mts/MTS_IO_CellularRadio.h index ed87a24..ab6e00a 100644 --- a/include/mts/MTS_IO_CellularRadio.h +++ b/include/mts/MTS_IO_CellularRadio.h @@ -55,6 +55,7 @@ namespace MTS { CODE getFirmware(std::string& sFirmware) override; CODE getFirmwareBuild(std::string& sFirmwareBuild) override; + CODE getVendorFirmware(std::string& sVendorFirmware) override; CODE getHardware(std::string& sHardware) override; CODE getManufacturer(std::string& sManufacturer) override; CODE getImei(std::string& sImei) override; @@ -99,6 +100,10 @@ namespace MTS { CODE updateDc(const Json::Value& jArgs, UpdateCb& stepCb) override; CODE updatePrl(const Json::Value& jArgs, UpdateCb& stepCb) override; CODE updateFumo(const Json::Value& jArgs, UpdateCb& stepCb) override; + CODE updateFumoLocal(int fd, UpdateCb& stepCb) override; + CODE fumoLocalInject(int fd, UpdateCb& stepCb) override; + CODE fumoLocalApply(UpdateCb& stepCb) override; + CODE fumoLocalCleanup() override; CODE resetHfa(const Json::Value& jArgs, UpdateCb& stepCb) override; CODE activate(const Json::Value& jArgs, UpdateCb& stepCb) override; CODE startOmaDm(UpdateCb& stepCb) override; @@ -179,6 +184,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() @@ -211,6 +218,10 @@ namespace MTS { std::string m_sRadioType; }; + static CODE getFileSize(int fd, size_t& nBytes); + static CODE sizeToChunks(const size_t nBytes, const size_t chunkSize, size_t& nChunks); + static CODE readChunk(int fd, char* pChunk, size_t dChunkSize, size_t& nReadBytes); + private: std::string m_sName; std::string m_sRadioPort; diff --git a/include/mts/MTS_IO_ICellularRadio.h b/include/mts/MTS_IO_ICellularRadio.h index f2d4dfe..085c217 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; @@ -118,6 +119,7 @@ namespace MTS { static const char *KEY_HARDWARE; //!< Radio Hardware Version static const char *KEY_FIRMWARE; //!< Radio Firmware Version static const char *KEY_FIRMWARE_BUILD;//!< Radio Firmware Build + static const char *KEY_VENDOR_FIRMWARE; //!< Radio Vendor Firmware Version static const char *KEY_IMEI; //!< International Mobile Station Equipment Identity static const char *KEY_MEID; //!< Mobile Equipment Identifier static const char *KEY_IMSI; //!< International Mobile Subscriber Identity @@ -220,6 +222,7 @@ namespace MTS { virtual CODE getFirmware(std::string& sFirmware) = 0; virtual CODE getFirmwareBuild(std::string& sFirmwareBuild) = 0; + virtual CODE getVendorFirmware(std::string& sVendorFirmware) = 0; virtual CODE getHardware(std::string& sHardware) = 0; virtual CODE getManufacturer(std::string& sManufacturer) = 0; virtual CODE getImei(std::string& sImei) = 0; @@ -427,6 +430,71 @@ namespace MTS { */ virtual CODE updateFumo(const Json::Value& jArgs, UpdateCb& stepCb) = 0; + /** + * @brief updateFumoLocal - Performs the radio firmware upgrade using local firmware image. + * + * This command uploads (injects) the whole delta firmware image to the radio, performs the + * upgrade and waits for it to complete. + * + * @param fd - file descriptor of a file that shall be injected to the radio. + * @param stepCb - callback to receive status updates during the firmware upgrade. + * @return CODE::SUCCESS when the firmware upgrade was successful, + * CODE::INVALID_ARGS when the file can't be opened for reading, + * CODE::FAILURE when upgrade failed on the radio side, + * CODE::NOT_APPLICABLE when not supported by this radio, + * CODE::ERROR otherwise. + */ + virtual CODE updateFumoLocal(int fd, UpdateCb& stepCb) = 0; + + /** + * @brief fumoLocalInject - upload delta file to the radio's internal memory. + * + * This command uploads (injects) the whole delta firmware image to some place that + * can be later used by the radio to perform the Delta Radio Firmware Upgrade. + * + * This delta firmware image is NOT validated on the firmware image upload step. + * + * @param fd - file sescriptor of a file that shall be uploaded to the radio. + * @param stepCb - callback to receive status updates during the upload. + * @return CODE::SUCCESS when the file was successfully uploaded, + * CODE::INVALID_ARGS when the file can't be opened for reading, + * CODE::NOT_APPLICABLE when not supported by this radio, + * CODE::ERROR otherwise. + */ + virtual CODE fumoLocalInject(int fd, UpdateCb& stepCb) = 0; + + /** + * @brief fumoLocalApply - apply the delta file that was previously uploaded. + * + * This commands initializes and tracks the progress of the delta firmware upgrade + * procedure using the delta firmware image file that was previously uploaded + * into internal memory of the radio. + * + * See ICellularRadio::fumoLocalInject to upload the file and prepare + * for the upgrade. + * + * @param fd - file descriptor of a file that shall be uploaded to the radio. + * @param stepCb - the callback to receive status updates during the upgrade. + * @return CODE::SUCCESS when the firmware upgrade was successful, + * CODE::FAILURE when upgrade failed on the radio side, + * CODE::NOT_APPLICABLE when not supported by this radio, + * CODE::ERROR otherwise. + */ + virtual CODE fumoLocalApply(UpdateCb& stepCb) = 0; + + /** + * @brief fumoLocalCleanup - remove the delta file from the radio's internal memory. + * + * This command allows to remove the old delta firmware image from the radio's internal + * memory for cases when it's no longer needed. + * + * @return CODE::SUCCESS when the file was successfully deleted, + * CODE::FAILURE when the file can't be deleted (i.e. no such file), + * CODE::NOT_APPLICABLE when not supported by this radio, + * CODE::ERROR otherwise. + */ + virtual CODE fumoLocalCleanup() = 0; + /* * jArgs = { * "msl" : "Master Subsidy Lock (Sprint): STRING" diff --git a/include/mts/MTS_IO_LE910C4EURadio.h b/include/mts/MTS_IO_LE910C4EURadio.h index e872df3..a0d6baa 100644 --- a/include/mts/MTS_IO_LE910C4EURadio.h +++ b/include/mts/MTS_IO_LE910C4EURadio.h @@ -35,6 +35,7 @@ namespace MTS { virtual ~LE910C4EURadio(){}; protected: + FOTA_GROUP getFotaGroup() override; private: diff --git a/include/mts/MTS_IO_ME910Radio.h b/include/mts/MTS_IO_ME910Radio.h index 09e8ede..93af86f 100644 --- a/include/mts/MTS_IO_ME910Radio.h +++ b/include/mts/MTS_IO_ME910Radio.h @@ -36,8 +36,6 @@ namespace MTS { virtual CODE setRxDiversity(const Json::Value& jArgs); - virtual CODE getFirmwareBuild(std::string& sFirmware); - protected: private: diff --git a/include/mts/MTS_IO_QuectelRadio.h b/include/mts/MTS_IO_QuectelRadio.h index fcee069..41c89dc 100644 --- a/include/mts/MTS_IO_QuectelRadio.h +++ b/include/mts/MTS_IO_QuectelRadio.h @@ -31,6 +31,7 @@ namespace MTS { public: bool resetRadio(uint32_t iTimeoutMillis = 5000) override; + CODE getVendorFirmware(std::string& sVendorFirmware) override; CODE getModel(std::string& sModel) override; CODE getIccid(std::string& sIccid) override; CODE getService(std::string& sService) override; @@ -45,6 +46,11 @@ namespace MTS { CODE setCellularMode(CELLULAR_MODES networks) override; + CODE updateFumoLocal(int fd, UpdateCb& stepCb) override; + CODE fumoLocalInject(int fd, UpdateCb& stepCb) override; + CODE fumoLocalCleanup() override; + CODE fumoLocalApply(UpdateCb& stepCb) override; + protected: QuectelRadio(const std::string& sName, const std::string& sRadioPort); @@ -54,10 +60,54 @@ namespace MTS { virtual CODE getServiceDomain(SERVICEDOMAIN& sd); virtual CODE convertToActiveBand(const std::string& sQuectelBand, ACTIVEBAND& band); + virtual CODE uploadFile(int fd, const std::string& sTargetFilename, UpdateCb& stepCb); + virtual CODE removeFile(const std::string& sTargetFilename); + virtual CODE checkFile(bool& bFilePresent, const std::string& sTargetFilename); + private: + // private variable to save old firmware versions during FOTA + std::string m_sQuectelFirmware; + + 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 inline void callNextStep(UpdateCb& stepCb, const char* csMessage); + static inline void callNextStep(UpdateCb& stepCb, const std::string& sMessage); + + 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); + CODE fumoWaitUpgradeFinished(UpdateCb& stepCb); + CODE fumoWaitNewFirmware(UpdateCb& stepCb); }; } } +void MTS::IO::QuectelRadio::callNextStep(ICellularRadio::UpdateCb& stepCb, const char* csMessage) { + if (stepCb) { + stepCb(Json::Value(csMessage)); + } +} + +void MTS::IO::QuectelRadio::callNextStep(ICellularRadio::UpdateCb& stepCb, const std::string& sMessage) { + if (stepCb) { + stepCb(Json::Value(sMessage)); + } +} + +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/include/mts/MTS_IO_TelitRadio.h b/include/mts/MTS_IO_TelitRadio.h index bd542f1..ffdddd9 100644 --- a/include/mts/MTS_IO_TelitRadio.h +++ b/include/mts/MTS_IO_TelitRadio.h @@ -30,6 +30,8 @@ namespace MTS { public: bool resetRadio(uint32_t iTimeoutMillis = 5000) override; + CODE getFirmwareBuild(std::string& sFirmwareBuild) override; + CODE getVendorFirmware(std::string& sVendorFirmware) override; CODE getModel(std::string& sModel) override; CODE getIccid(std::string& sIccid) override; CODE getService(std::string& sService) override; @@ -44,6 +46,10 @@ namespace MTS { CODE getSupportedCellularModes(CELLULAR_MODES &networks) override; CODE setCellularMode(CELLULAR_MODES networks) override; + CODE updateFumoLocal(int fd, UpdateCb& stepCb) override; + CODE fumoLocalInject(int fd, UpdateCb& stepCb) override; + CODE fumoLocalApply(UpdateCb& stepCb) override; + protected: TelitRadio(const std::string& sName, const std::string& sRadioPort); @@ -53,10 +59,51 @@ namespace MTS { CODE getIsSimInserted(bool& bData) override; CODE getSimLockAttempts(int& iAttemptsPin, int& iAttemptsPuk) override; + enum FOTA_GROUP : uint8_t { + VALUE_GROUP_A = 0, + VALUE_GROUP_B, + VALUE_GROUP_C, + VALUE_GROUP_D, + VALUE_UNKNOWN + }; + + virtual FOTA_GROUP getFotaGroup(); + virtual CODE fumoWriteGroupsABD(int fd, UpdateCb& stepCb); + //virtual CODE fumoWriteGroupC(int fd, UpdateCb& stepCb); + private: virtual CODE getSimLockAttempts(int& iAttemptsPin, int& iAttemptsPuk, const std::string& sLockStatus); ICellularRadio::CODE wdsList(std::set<int> &wds); + + // private variable to save old firmware versions during FOTA + std::string m_sTelitFirmware; + + static const size_t FILE_CHUNK_SIZE; + static const std::string CMD_ABORT_UPLOAD; + + CODE startFotaWriteABD(); + CODE abortFotaWriteABD(); + + static inline void callNextStep(UpdateCb& stepCb, const char* csMessage); + static inline void callNextStep(UpdateCb& stepCb, const std::string& sMessage); + + CODE fumoWaitUpgradeFinished(UpdateCb& stepCb); + CODE fumoCheckNewFirmware(UpdateCb& stepCb); + }; } } -#endif + +void MTS::IO::TelitRadio::callNextStep(ICellularRadio::UpdateCb& stepCb, const char* csMessage) { + if (stepCb) { + stepCb(Json::Value(csMessage)); + } +} + +void MTS::IO::TelitRadio::callNextStep(ICellularRadio::UpdateCb& stepCb, const std::string& sMessage) { + if (stepCb) { + stepCb(Json::Value(sMessage)); + } +} + +#endif /* MTS_IO_TELITRADIO_H_ */ diff --git a/src/MTS_IO_CellularRadio.cpp b/src/MTS_IO_CellularRadio.cpp index b18478e..49a2b32 100644 --- a/src/MTS_IO_CellularRadio.cpp +++ b/src/MTS_IO_CellularRadio.cpp @@ -21,6 +21,7 @@ #include "mts/MTS_IO_CellularRadio.h" #include <unistd.h> +#include <sys/stat.h> #include <mts/MTS_IO_MccMncTable.h> #include <mts/MTS_Thread.h> @@ -200,6 +201,11 @@ ICellularRadio::CODE CellularRadio::getFirmwareBuild(std::string& sFirmwareBuild return FAILURE; } +ICellularRadio::CODE CellularRadio::getVendorFirmware(std::string& sVendorFirmware) { + sVendorFirmware = ICellularRadio::VALUE_NOT_SUPPORTED; + return FAILURE; +} + ICellularRadio::CODE CellularRadio::getHardware(std::string& sHardware) { printTrace("%s| Get Hardware", m_sName.c_str()); sHardware = ICellularRadio::VALUE_NOT_SUPPORTED; @@ -1051,6 +1057,30 @@ ICellularRadio::CODE CellularRadio::updateFumo(const Json::Value&, UpdateCb&) { return NOT_APPLICABLE; } +ICellularRadio::CODE CellularRadio::updateFumoLocal(int, ICellularRadio::UpdateCb&) { + printTrace("%s| Update Local Firmware Update Management Object", m_sName.c_str()); + + return NOT_APPLICABLE; +} + +ICellularRadio::CODE CellularRadio::fumoLocalInject(int, ICellularRadio::UpdateCb&) { + printTrace("%s| Inject Delta Firmware Image File: not applicable", m_sName.c_str()); + + return NOT_APPLICABLE; +} + +ICellularRadio::CODE CellularRadio::fumoLocalApply(ICellularRadio::UpdateCb&) { + printTrace("%s| Apply Delta Firmware Image File: not applicable", m_sName.c_str()); + + return NOT_APPLICABLE; +} + +ICellularRadio::CODE CellularRadio::fumoLocalCleanup() { + printTrace("%s| Cleanup Delta Firmware Image 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()); @@ -1103,6 +1133,34 @@ 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; + } + + // This limit comes from the Connection::write implementation. Otherwise we can get overflows. + const size_t maxInt32 = INT32_MAX; // iSize parameter type in Connection::write + const size_t maxUInt32 = UINT32_MAX; // return value type in Connection::write + const size_t nSizeLimit = std::min(maxInt32, maxUInt32); + + if (nBytes > nSizeLimit) { + printError("RADIO| Chunks larger than %d bytes are not supported", nSizeLimit); + return INVALID_ARGS; + } + + // Now we can ignore conversion and overflow warnings emitted by compiler. + int32_t iResult; + iResult = m_apIo->write(pData, nBytes); + + if(iResult != static_cast<int32_t>(nBytes)) { + 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); @@ -1221,3 +1279,78 @@ const char *CellularRadio::RadioBandMap::getRadioBandName(const std::string &cha return band; } + +ICellularRadio::CODE CellularRadio::getFileSize(int fd, size_t& nBytes) { + CODE rc = FAILURE; + + do { + struct stat fileStatus; + + // On success, zero is returned. On error, -1 is returned, and errno is set appropriately. + if (fstat(fd, &fileStatus) < 0) { + printError("Failed to determine file size: %d", errno); + break; + } + + if (fileStatus.st_size < 0) { + printError("Failed to determine file size, file size is negative: %d", fileStatus.st_size); + break; + } + + nBytes = static_cast<size_t>(fileStatus.st_size); + rc = SUCCESS; + + } while (false); + + lseek(fd, 0, SEEK_SET); + return rc; +} + +ICellularRadio::CODE CellularRadio::sizeToChunks(const size_t nBytes, const size_t chunkSize, size_t& nChunks) { + nChunks = (nBytes + chunkSize - 1) / chunkSize; + return SUCCESS; +} + +ICellularRadio::CODE CellularRadio::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; +} diff --git a/src/MTS_IO_ICellularRadio.cpp b/src/MTS_IO_ICellularRadio.cpp index 1a428e4..9dbfad1 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/"; @@ -33,6 +33,7 @@ const char *MTS::IO::ICellularRadio::KEY_MANUFACTURER = "manufacturer"; //!< const char *MTS::IO::ICellularRadio::KEY_HARDWARE = "hardware"; //!< Radio Hardware Version const char *MTS::IO::ICellularRadio::KEY_FIRMWARE = "firmware"; //!< Radio Firmware Version const char *MTS::IO::ICellularRadio::KEY_FIRMWARE_BUILD = "firmwarebuild"; //!< Radio Firmware Build +const char *MTS::IO::ICellularRadio::KEY_VENDOR_FIRMWARE = "vendorfirmware"; //!< Radio Vendor Firmware Version const char *MTS::IO::ICellularRadio::KEY_CARRIER = "carrier"; //!< Cellular Service Provider = Home Network const char *MTS::IO::ICellularRadio::VALUE_CARRIER_VERIZON = "Verizon"; diff --git a/src/MTS_IO_LE910C4EURadio.cpp b/src/MTS_IO_LE910C4EURadio.cpp index 029a921..4cf146b 100644 --- a/src/MTS_IO_LE910C4EURadio.cpp +++ b/src/MTS_IO_LE910C4EURadio.cpp @@ -29,3 +29,7 @@ LE910C4EURadio::LE910C4EURadio(const std::string& sPort) { } + +TelitRadio::FOTA_GROUP LE910C4EURadio::getFotaGroup() { + return VALUE_GROUP_B; +} diff --git a/src/MTS_IO_ME910Radio.cpp b/src/MTS_IO_ME910Radio.cpp index 2b181d2..ad3e7ec 100644 --- a/src/MTS_IO_ME910Radio.cpp +++ b/src/MTS_IO_ME910Radio.cpp @@ -33,35 +33,3 @@ ME910Radio::ME910Radio(const std::string& sME910Model, const std::string& sPort) ICellularRadio::CODE ME910Radio::setRxDiversity(const Json::Value& jArgs) { return FAILURE; } - - -ICellularRadio::CODE ME910Radio::getFirmwareBuild(std::string& sFirmwareBuild) { - std::string sCmd("AT#CFVR"); - - std::string sResult = sendCommand(sCmd); - - size_t end = sResult.find(ICellularRadio::RSP_OK); - if (end == std::string::npos) { - printWarning("%s| Unable to get firmware build number [%s]", - getName().c_str(), - sCmd.c_str()); - return FAILURE; - } - - size_t start = sResult.find("#CFVR:"); - if (start == std::string::npos) { - printWarning("%s| Command returned unexpected response [%s]", - getName().c_str(), - sCmd.c_str()); - return FAILURE; - } - - start += sizeof("#CFVR:"); - - sFirmwareBuild = MTS::Text::trim(sResult.substr(start, end-start)); - if(sFirmwareBuild.size() == 0) { - printWarning("%s| Firmware Build Version is empty", getName().c_str()); - return FAILURE; - } - return SUCCESS; -} diff --git a/src/MTS_IO_QuectelRadio.cpp b/src/MTS_IO_QuectelRadio.cpp index 5c2c352..39c0601 100644 --- a/src/MTS_IO_QuectelRadio.cpp +++ b/src/MTS_IO_QuectelRadio.cpp @@ -25,9 +25,19 @@ #include <mts/MTS_Logger.h> #include <mts/MTS_Thread.h> #include <mts/MTS_Text.h> +#include <mts/MTS_Timer.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) { @@ -46,6 +56,26 @@ bool QuectelRadio::resetRadio(uint32_t iTimeoutMillis) { return false; } +ICellularRadio::CODE QuectelRadio::getVendorFirmware(std::string& sVendorFirmware) { + printTrace("%s| Get Quectel-specific firmware version", getName().c_str()); + sVendorFirmware = ICellularRadio::VALUE_NOT_SUPPORTED; + std::string sCmd("AT+QGMR"); + std::string sResult = sendCommand(sCmd); + size_t pos = sResult.find(ICellularRadio::RSP_OK); + if (pos == std::string::npos) { + printWarning("%s| Unable to get firmware from radio using command [%s]", getName().c_str(), sCmd.c_str()); + return FAILURE; + } + + sVendorFirmware = MTS::Text::trim(sResult.substr(0, pos)); + if(sVendorFirmware.size() == 0) { + printWarning("%s| Unable to get firmware from radio using command [%s]", getName().c_str(), sCmd.c_str()); + return FAILURE; + } + + return SUCCESS; +} + ICellularRadio::CODE QuectelRadio::getModel(std::string& sModel) { printTrace("%s| Get Model", getName().c_str()); //Always returns SUCCESS because the model should be m_sName @@ -466,9 +496,7 @@ ICellularRadio::CODE QuectelRadio::startOmaDm(ICellularRadio::UpdateCb& stepCb) eCode = sendBasicCommand(sCmdOdmStart, iTimeoutOk); if (eCode != SUCCESS) { printError("%s| OMA DM procedure can not be started", getName().c_str()); - if (stepCb) { - stepCb(Json::Value("OMA DM Error: OMA DM can not be started")); - } + callNextStep(stepCb, "OMA DM Error: OMA DM can not be started"); break; } @@ -479,18 +507,14 @@ ICellularRadio::CODE QuectelRadio::startOmaDm(ICellularRadio::UpdateCb& stepCb) // Received something unexpected or nothing at all? if (sResponse.find(sOdmStarted) == std::string::npos) { printError("%s| OMA DM procedure failed due to timeout", getName().c_str()); - if (stepCb) { - stepCb(Json::Value("OMA DM Error: OMA DM failed due to timeout")); - } + callNextStep(stepCb, "OMA DM Error: OMA DM failed due to timeout"); eCode = FAILURE; break; } // Got "DM Started" message from the radio printTrace("%s| OMA DM started", getName().c_str()); - if (stepCb) { - stepCb(Json::Value("OMA DM Info: OMA DM started")); - } + callNextStep(stepCb, "OMA DM Info: OMA DM started"); // Wait for the "End" or "Abnormal" response sResponse = sendCommand("", vOdmFinishedStrings, iTimeoutEnd, 0x00); @@ -499,9 +523,8 @@ ICellularRadio::CODE QuectelRadio::startOmaDm(ICellularRadio::UpdateCb& stepCb) // Received "Abnormal"? if (sResponse.find(sOdmAbnormal) != std::string::npos) { printError("%s| OMA DM procedure failed due to internal error: [%s]", getName().c_str(), sResponse.c_str()); - if (stepCb) { - stepCb(Json::Value("OMA DM Error: OMA DM failed due to internal error")); - } + callNextStep(stepCb, "OMA DM Error: OMA DM failed due to internal error"); + eCode = FAILURE; break; } @@ -509,9 +532,8 @@ ICellularRadio::CODE QuectelRadio::startOmaDm(ICellularRadio::UpdateCb& stepCb) // Received something unexpected or nothing at all? if (sResponse.find(sOdmFinished) == std::string::npos) { printError("%s| OMA DM procedure failed due to timeout", getName().c_str()); - if (stepCb) { - stepCb(Json::Value("OMA DM Error: OMA DM failed due to timeout")); - } + callNextStep(stepCb, "OMA DM Error: OMA DM failed due to timeout"); + sendBasicCommand(sCmdOdmAbort, iTimeoutAbort); // abort the procedure eCode = FAILURE; break; @@ -519,9 +541,7 @@ ICellularRadio::CODE QuectelRadio::startOmaDm(ICellularRadio::UpdateCb& stepCb) // Got "DM End" message from the radio printTrace("%s| OMA DM finished", getName().c_str()); - if (stepCb) { - stepCb(Json::Value("OMA DM Info: OMA DM finished")); - } + callNextStep(stepCb, "OMA DM Info: OMA DM finished"); eCode = SUCCESS; @@ -530,6 +550,143 @@ ICellularRadio::CODE QuectelRadio::startOmaDm(ICellularRadio::UpdateCb& stepCb) return eCode; } +ICellularRadio::CODE QuectelRadio::updateFumoLocal(int fd, ICellularRadio::UpdateCb& stepCb) { + CODE rc; + + rc = fumoLocalInject(fd, stepCb); + if (rc != SUCCESS) { + return rc; + } + + rc = fumoLocalApply(stepCb); + (void)fumoLocalCleanup(); // try to cleanup the injected file but cleanup errors are not fatal + + return rc; +} + +ICellularRadio::CODE QuectelRadio::fumoLocalInject(int fd, ICellularRadio::UpdateCb& stepCb) { + CODE rc = FAILURE; + bool bIsFilePresent = false; + + do { + callNextStep(stepCb, "FUMO Info: downloading the firmware"); + + rc = checkFile(bIsFilePresent, VALUE_MTS_DELTA_NAME); + if (rc != SUCCESS) { + printError("Failed to check if the delta file was already download."); + callNextStep(stepCb, "FUMO Error: failed to download the firmware file"); + break; + } + + if (bIsFilePresent) { + rc = fumoLocalCleanup(); + } + + if (rc != SUCCESS) { + printError("Failed to remove the previous delta file."); + callNextStep(stepCb, "FUMO Error: failed to download the firmware file"); + break; + } + + rc = uploadFile(fd, VALUE_MTS_DELTA_NAME, stepCb); + if (rc != SUCCESS) { + printError("Failed to inject the delta file."); + callNextStep(stepCb, "FUMO Error: failed to download the firmware file"); + break; + } + + callNextStep(stepCb, "FUMO Info: firmware downloaded successfully"); + + } while (false); + + return rc; +} + +ICellularRadio::CODE QuectelRadio::fumoLocalCleanup() { + printTrace("Removing the delta upgrade file: %s", VALUE_MTS_DELTA_NAME.c_str()); + return removeFile(VALUE_MTS_DELTA_NAME); +} + +ICellularRadio::CODE QuectelRadio::fumoLocalApply(ICellularRadio::UpdateCb& stepCb) { + ICellularRadio::CODE rc; + std::string sCmd; + + rc = getVendorFirmware(m_sQuectelFirmware); + if (rc != SUCCESS) { + callNextStep(stepCb, "FUMO Error: Failed to obtain current firmware version"); + return rc; + } + printInfo("Current firmware version: %s", m_sQuectelFirmware.c_str()); + + // Send "AT+QFOTADL" command to start the upgrade. OK response follows shortly. + sCmd = "AT+QFOTADL=\""; + sCmd += VALUE_MTS_DELTA_PATH; + sCmd += "\""; + + rc = sendBasicCommand(sCmd, 10000); + + if (rc != SUCCESS) { + printError("FUMO failed, OK not received from the radio"); + callNextStep(stepCb, "FUMO Error: failed to apply the firmware"); + 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 int dMaxAttempts = 5; // the radio makes 5 attempts to update the firmware + + for (int i = 0; i < dMaxAttempts; i++) { + + 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); + + // It's now detached. Try to reconnect + if (!resetConnection(duAttachTimeout)) { + printError("Can't connect to the radio in %d ms", (duAttachTimeout)); + callNextStep(stepCb, "FUMO Error: unable to obtain radio after reset"); + break; + } + + // It's now back on the bus. Wait for the URC messages. + printInfo("Applying the radio firmware"); + callNextStep(stepCb, "FUMO Info: applying the radio firmware"); + rc = fumoWaitUpgradeFinished(stepCb); + + if (rc == ERROR) { + // unrecoverable error + callNextStep(stepCb, "FUMO Error: failed to apply the firmware, consider radio reset"); + break; + } + + if (rc != SUCCESS) { + // attempt failed, radio reboots and starts its next attempt + printError("Failed to apply the firmware, attempts left: %d", (dMaxAttempts - i - 1)); + callNextStep(stepCb, "FUMO Error: failed to apply the firmware"); + continue; + } + + // Wait for the radio to finish update and reboot + printTrace("Waiting for the radio to come up"); + callNextStep(stepCb, "FUMO Info: waiting for the radio to enter normal mode"); + + rc = fumoWaitNewFirmware(stepCb); + break; + } + + if (rc == SUCCESS) { + printInfo("Radio firmware applied successfully"); + callNextStep(stepCb, "FUMO Done: radio firmware applied successfully"); + } else { + printError("Radio firmware has not been updated"); + callNextStep(stepCb, "FUMO Error: radio firmware has not been updated"); + } + + return rc; +} + ICellularRadio::CODE QuectelRadio::getServiceDomain(ICellularRadio::SERVICEDOMAIN& sd) { printTrace("%s| Get Service Domain", getName().c_str()); @@ -694,3 +851,266 @@ ICellularRadio::CODE QuectelRadio::setCellularMode(CELLULAR_MODES networks) { } return SUCCESS; } + +ICellularRadio::CODE QuectelRadio::uploadFile(int fd, const std::string& sTargetFilename, ICellularRadio::UpdateCb& stepCb) { + size_t dPayloadLength; + size_t nChunks; + CODE rc; + + rc = getFileSize(fd, dPayloadLength); + if (rc != SUCCESS) { + return rc; + } + + rc = sizeToChunks(dPayloadLength, FILE_CHUNK_SIZE, nChunks); + if (rc != SUCCESS) { + return rc; + } + + printTrace("File size: %d bytes and %d chunks", dPayloadLength, nChunks); + printTrace("Starting file upload..."); + + rc = startFileUpload(sTargetFilename, dPayloadLength); + if (rc != SUCCESS) { + return rc; + } + + printTrace("File upload started."); + callNextStep(stepCb, "FILE Info: Started file upload for " + sTargetFilename); + + uint16_t dChecksum = 0; + size_t nChunksPerCent = (nChunks / 100) + 1; + size_t nFragmentLength = 0; + std::array<char, FILE_CHUNK_SIZE> vBuffer; + + for (size_t iChunk = 1; iChunk < (nChunks + 1); iChunk++) { + + rc = readChunk(fd, vBuffer.data(), vBuffer.size(), nFragmentLength); + if (rc != SUCCESS) { + break; + } + + // we got our fragment, calculate checksum and flush the data + uint16_t dFragmentChecksum = getQuectelChecksum(vBuffer.data(), nFragmentLength); + updateQuectelChecksum(dChecksum, dFragmentChecksum); + + rc = sendData(vBuffer.data(), nFragmentLength); + if (rc != SUCCESS) { + break; + } + + if (stepCb && ((iChunk % nChunksPerCent) == 0)) { + size_t dPercentsCompleted = iChunk / nChunksPerCent; + callNextStep(stepCb, "FILE Info: Uploaded " + MTS::Text::format(dPercentsCompleted) + "%"); + } + + } + + 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); + + 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 (rc == SUCCESS) { + callNextStep(stepCb, "FILE Info: Upload finished successfully"); + } else { + callNextStep(stepCb, "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. + bool bHasLowByte = (i + 1 < nBytes); + + uint8_t high = castData[i]; + uint8_t low = bHasLowByte ? (castData[i+1]) : (0); + + uint16_t iFragment = bytesToUint16(high, low); + updateQuectelChecksum(iChecksum, iFragment); + } + + return iChecksum; +} + +ICellularRadio::CODE QuectelRadio::fumoWaitUpgradeFinished(ICellularRadio::UpdateCb& stepCb) { + const uint32_t duUrcTimeout = 4 * 60 * 1000; // wait for 4 minutes for the next URC message + const std::string sFotaUrcPrefix = "+QIND: \"FOTA\""; // prefix for the URC notification messages + const std::string sFotaUrcStart = "\"START\""; + const std::string sFotaUrcProgress = "\"UPDATING\""; + const std::string sFotaUrcEnd = "\"END\""; + const std::vector<std::string> vFotaBailStrings{ sFotaUrcPrefix }; + + CODE rc = FAILURE; + std::string sResponse; + + while (true) { // breaks on "FOTA","END" + sResponse = sendCommand("", vFotaBailStrings, duUrcTimeout, 0x00); + printTrace("Radio response: [%s]", sResponse.c_str()); + + if (sResponse.find(sFotaUrcPrefix) == std::string::npos) { + printError("No URC messages from the radio in %d ms", duUrcTimeout); + callNextStep(stepCb, "FUMO Error: timeout, radio is not responding"); + rc = ERROR; + break; + } + + const auto vParts = MTS::Text::split(MTS::Text::trim(sResponse), ',', 3); + const std::string& sStage = vParts[1]; + if (sStage == sFotaUrcEnd) { + // FOTA finished + printTrace("Got FOTA END message"); + const std::string& sCode = vParts[2]; + + if (sCode == "0") { + // finished successfully + rc = SUCCESS; + break; + } + // attempt failed, the radio attempts to recover + callNextStep(stepCb, "FUMO Error: radio returned error code " + sCode); + break; + } else if (sStage == sFotaUrcStart) { + printTrace("Got FOTA START message"); + } else if (sStage == sFotaUrcProgress) { + printTrace("Got FOTA progress message"); + const std::string& sPercents = vParts[2]; + printInfo("FOTA progress: [%s]", sPercents.c_str()); + callNextStep(stepCb, "FUMO Info: firmware apply progress " + sPercents); + } else { + printInfo("FOTA unexpected URC code: [%s]", sStage.c_str()); + } + } + + return rc; +} + +ICellularRadio::CODE QuectelRadio::fumoWaitNewFirmware(ICellularRadio::UpdateCb& stepCb) { + MTS::Timer oTimer; + oTimer.start(); + std::string sQuectelFirmware; + CODE rc = ERROR; + + while (oTimer.getSeconds() < (5 * 60)) { // 5 minutes + + MTS::Thread::sleep(10000); + + if (getVendorFirmware(sQuectelFirmware) != SUCCESS) { + // The radio is probably unavailable + resetConnection(100); + continue; + } + + printInfo("Firmware version before the upgrade: %s", m_sQuectelFirmware.c_str()); + printInfo("Current firmware version: %s", sQuectelFirmware.c_str()); + + if (sQuectelFirmware == m_sQuectelFirmware) { + // Radio will not reset anymore, firmware version left the same, not updated + printError("Radio firmware version not changed after upgrade"); + rc = FAILURE; + break; + } + + // The firmware numbers have changed + rc = SUCCESS; + break; + } + oTimer.stop(); + + if (rc == ERROR) { + printError("Radio is not responding"); + callNextStep(stepCb, "FUMO Error: unable to obtain radio after reset"); + } + + 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, 0x00); +} diff --git a/src/MTS_IO_TelitRadio.cpp b/src/MTS_IO_TelitRadio.cpp index be31b03..457dfdc 100644 --- a/src/MTS_IO_TelitRadio.cpp +++ b/src/MTS_IO_TelitRadio.cpp @@ -24,8 +24,14 @@ #include <mts/MTS_Thread.h> #include <mts/MTS_Text.h> +#include <unistd.h> + using namespace MTS::IO; +const size_t TelitRadio::FILE_CHUNK_SIZE = 1024; +const std::string TelitRadio::CMD_ABORT_UPLOAD = "+++"; + + TelitRadio::TelitRadio(const std::string& sName, const std::string& sRadioPort) : CellularRadio(sName, sRadioPort) { @@ -45,6 +51,80 @@ bool TelitRadio::resetRadio(uint32_t iTimeoutMillis) { return false; } +ICellularRadio::CODE TelitRadio::getFirmwareBuild(std::string& sFirmwareBuild) { + std::string sCmd("AT#CFVR"); + + std::string sResult = sendCommand(sCmd); + + size_t end = sResult.find(ICellularRadio::RSP_OK); + if (end == std::string::npos) { + printWarning("%s| Unable to get firmware build number [%s]", + getName().c_str(), + sCmd.c_str()); + return FAILURE; + } + + size_t start = sResult.find("#CFVR:"); + if (start == std::string::npos) { + printWarning("%s| Command returned unexpected response [%s]", + getName().c_str(), + sCmd.c_str()); + return FAILURE; + } + + start += sizeof("#CFVR:"); + + sFirmwareBuild = MTS::Text::trim(sResult.substr(start, end-start)); + if(sFirmwareBuild.size() == 0) { + printWarning("%s| Firmware Build Version is empty", getName().c_str()); + return FAILURE; + } + return SUCCESS; +} + +ICellularRadio::CODE TelitRadio::getVendorFirmware(std::string& sVendorFirmware) { + printTrace("%s| Get Telit-specific firmware version", getName().c_str()); + ICellularRadio::CODE rc = FAILURE; + sVendorFirmware = ICellularRadio::VALUE_NOT_SUPPORTED; + std::string sFirmware; + std::string sFirmwareBuild; + std::string sCmd("AT#SWPKGV"); + std::string sResult = sendCommand(sCmd); + size_t pos = sResult.find(ICellularRadio::RSP_OK); + + do { + + if (pos != std::string::npos) { + // Found + std::vector<std::string> vLine = MTS::Text::split(sResult, "\r"); + sVendorFirmware = MTS::Text::trim(vLine[1]); + if(sVendorFirmware.size() == 0) { + printWarning("%s| Unable to get firmware from radio using command [%s]", getName().c_str(), sCmd.c_str()); + rc = FAILURE; + } else { + rc = SUCCESS; + } + break; + } + + // Not Found. Then we will use "AT+CGMR" + "AT#CFVR" + rc = getFirmware(sFirmware); + if (rc != SUCCESS){ + break; + } + + rc = getFirmwareBuild(sFirmwareBuild); + if (rc != SUCCESS){ + break; + } + + sVendorFirmware = sFirmware + "," + sFirmwareBuild; + + } while (false); + + + return rc; +} ICellularRadio::CODE TelitRadio::getModel(std::string& sModel) { printTrace("%s| Get Model", getName().c_str()); @@ -730,3 +810,276 @@ ICellularRadio::CODE TelitRadio::setCellularMode(CELLULAR_MODES networks) { } return SUCCESS; } + +ICellularRadio::CODE TelitRadio::updateFumoLocal(int fd, ICellularRadio::UpdateCb& stepCb) { + CODE rc = FAILURE; + + rc = fumoLocalInject(fd, stepCb); + if (rc != SUCCESS) { + return rc; + } + + rc = fumoLocalApply(stepCb); + + return rc; +} + +ICellularRadio::CODE TelitRadio::fumoLocalInject(int fd, ICellularRadio::UpdateCb& stepCb) { + CODE rc = FAILURE; + FOTA_GROUP group = getFotaGroup(); + + do { + callNextStep(stepCb, "FUMO Info: downloading the firmware"); + + if ((group == VALUE_GROUP_A) || (group == VALUE_GROUP_B) || (group == VALUE_GROUP_D)) { + rc = fumoWriteGroupsABD(fd, stepCb); + if (rc != SUCCESS) { + printError("Failed to inject the delta file."); + callNextStep(stepCb, "FUMO Error: failed to download the firmware file"); + break; + } + } else if (group == VALUE_GROUP_C) { + //TODO Not Implemented TelitRadio::fumoWriteGroupC + printError("Failed to inject the delta file."); + callNextStep(stepCb, "FUMO Error: not implemented"); + rc = NOT_APPLICABLE; + break; + } else { + printError("Delta firmware upgrade is not supported for this type of radio modem"); + callNextStep(stepCb, "FUMO Error: delta firmware upgrade is not supported for this type of radio modem"); + rc = NOT_APPLICABLE; + break; + } + + callNextStep(stepCb, "FUMO Info: firmware downloaded successfully"); + + } while (false); + + return rc; +} + +ICellularRadio::CODE TelitRadio::fumoLocalApply(ICellularRadio::UpdateCb& stepCb) { + ICellularRadio::CODE rc; + std::string sCmd; + FOTA_GROUP group = getFotaGroup(); + + rc = getVendorFirmware(m_sTelitFirmware); + if (rc != SUCCESS) { + callNextStep(stepCb, "FUMO Error: Failed to obtain current firmware version"); + return rc; + } + printInfo("Current firmware version: %s", m_sTelitFirmware.c_str()); + + if ((group == VALUE_GROUP_A) || (group == VALUE_GROUP_D)) { + // Send "AT#OTAUP=0,0" command to start the upgrade. OK response follows shortly. + sCmd = "AT#OTAUP=0,0"; + } else if ((group == VALUE_GROUP_B) || (group == VALUE_GROUP_C)) { + // Send "AT#OTAUP=2" command to start the upgrade. OK response follows shortly. + sCmd = "AT#OTAUP=2"; + } else { + printError("Delta firmware upgrade is not supported for this type of radio modem"); + callNextStep(stepCb, "FUMO Error: delta firmware upgrade is not supported for this type of radio modem"); + return NOT_APPLICABLE; + } + + rc = sendBasicCommand(sCmd, 10000); + + if (rc != SUCCESS) { + printError("FUMO failed, OK not received from the radio"); + callNextStep(stepCb, "FUMO Error: failed to apply the firmware"); + return rc; + } + + const uint32_t duDetachTimeout = 10000; // wait for 10 seconds for the radio to detach + + do { + printInfo("Applying the radio firmware"); + callNextStep(stepCb, "FUMO Info: applying the radio firmware"); + + // Wait for the radio to detach from the USB bus + MTS::Thread::sleep(duDetachTimeout); + + rc = fumoWaitUpgradeFinished(stepCb); + + if (rc != SUCCESS) { + break; + } + + rc = fumoCheckNewFirmware(stepCb); + + } while (false); + + + if (rc == SUCCESS) { + printInfo("Radio firmware applied successfully"); + callNextStep(stepCb, "FUMO Done: radio firmware applied successfully"); + } else { + printError("Radio firmware has not been updated"); + callNextStep(stepCb, "FUMO Error: radio firmware has not been updated"); + } + + return rc; +} + +TelitRadio::FOTA_GROUP TelitRadio::getFotaGroup() { + return VALUE_UNKNOWN; +} + +ICellularRadio::CODE TelitRadio::fumoWriteGroupsABD(int fd, ICellularRadio::UpdateCb& stepCb) { + size_t dPayloadLength; + size_t nChunks; + CODE rc; + + rc = getFileSize(fd, dPayloadLength); + if (rc != SUCCESS) { + return rc; + } + + rc = sizeToChunks(dPayloadLength, FILE_CHUNK_SIZE, nChunks); + if (rc != SUCCESS) { + return rc; + } + + printTrace("File size: %d bytes and %d chunks", dPayloadLength, nChunks); + printTrace("Starting file upload..."); + + rc = startFotaWriteABD(); + if (rc != SUCCESS) { + return rc; + } + + printTrace("File upload started."); + callNextStep(stepCb, "FILE Info: Started file upload"); + + size_t nChunksPerCent = (nChunks / 100) + 1; + size_t nFragmentLength = 0; + std::array<char, FILE_CHUNK_SIZE> vBuffer; + + for (size_t iChunk = 1; iChunk < (nChunks + 1); iChunk++) { + + rc = readChunk(fd, vBuffer.data(), vBuffer.size(), nFragmentLength); + if (rc != SUCCESS) { + break; + } + + rc = sendData(vBuffer.data(), nFragmentLength); + if (rc != SUCCESS) { + break; + } + + if (stepCb && ((iChunk % nChunksPerCent) == 0)) { + size_t dPercentsCompleted = iChunk / nChunksPerCent; + callNextStep(stepCb, "FILE Info: Uploaded " + MTS::Text::format(dPercentsCompleted) + "%"); + } + } + + if (rc != SUCCESS) { + callNextStep(stepCb, "FILE Error: Upload failed due to internal error"); + } else { + callNextStep(stepCb, "FILE Info: Upload finished successfully"); + } + + // send +++ + abortFotaWriteABD(); + return rc; +} + + +ICellularRadio::CODE TelitRadio::startFotaWriteABD() { + const std::vector<std::string> vBailStrings{ ICellularRadio::RSP_CONNECT, ICellularRadio::RSP_ERROR }; + const int dTimeout = 1000; //ms + std::string sCommand, sResult; + + sCommand = "AT#OTAUPW"; + + 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 TelitRadio::abortFotaWriteABD() { + /* + * 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, 0x00); +} + +ICellularRadio::CODE TelitRadio::fumoWaitUpgradeFinished(ICellularRadio::UpdateCb& stepCb) { + const uint32_t duAttachTimeout = 6 * 60 * 1000;// wait for 6 minutes for the radio to attach + const uint32_t duUrcTimeout = 60 * 1000; // wait for 1 minutes for the next URC message + const std::string sFotaUrcPrefix = "#OTAEV:"; // prefix for the URC notification messages + const std::string sFotaUrcEndSuccess = "Module Upgraded To New Fw"; + const std::string sFotaUrcEndFailed = "OTA Fw Upgrade Failed"; + const std::vector<std::string> vFotaBailStrings{ sFotaUrcPrefix }; + + CODE rc = FAILURE; + std::string sResponse; + + // It's now detached. Try to reconnect + if (!resetConnection(duAttachTimeout)) { + printError("Can't connect to the radio in %d ms", (duAttachTimeout)); + callNextStep(stepCb, "FUMO Error: unable to obtain radio after reset"); + return ERROR; + } + + while (true) { + + sResponse = sendCommand("", vFotaBailStrings, duUrcTimeout, 0x00); + printTrace("Radio response: [%s]", sResponse.c_str()); + + if (sResponse.find(sFotaUrcPrefix) == std::string::npos) { + printError("No URC messages from the radio in %d ms", duUrcTimeout); + callNextStep(stepCb, "FUMO Error: timeout, radio is not responding"); + rc = ERROR; + break; + } + + if (sResponse.find(sFotaUrcEndSuccess) != std::string::npos) { + printTrace("Radio module upgraded to new firmware"); + callNextStep(stepCb, "FUMO Info: radio module upgraded to new firmware"); + rc = SUCCESS; + break; + } + + if (sResponse.find(sFotaUrcEndFailed) != std::string::npos) { + printTrace("Radio module firmware upgrade failed"); + callNextStep(stepCb, "FUMO Error: firmware upgrade failed"); + rc = ERROR; + break; + } + + } + + return rc; +} + +ICellularRadio::CODE TelitRadio::fumoCheckNewFirmware(ICellularRadio::UpdateCb& stepCb) { + CODE rc = SUCCESS; + std::string sTelitFirmware; + + rc = getVendorFirmware(sTelitFirmware); + + if (rc != SUCCESS) { + callNextStep(stepCb, "FUMO Error: Failed to obtain current firmware version"); + return rc; + } + + printInfo("Firmware version before the upgrade: %s", m_sTelitFirmware.c_str()); + printInfo("Current firmware version: %s", sTelitFirmware.c_str()); + + if (sTelitFirmware == m_sTelitFirmware) { + // Radio will not reset anymore, firmware version left the same, not updated + printError("Radio firmware version not changed after upgrade"); + rc = FAILURE; + } + + return rc; +} |