summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJeff Hatch <jhatch@multitech.com>2020-07-22 08:25:08 -0500
committerJeff Hatch <jhatch@multitech.com>2020-07-22 08:25:08 -0500
commitd340135922da6eb881418824e470cbdba835b498 (patch)
tree90e162e6cf2c6a1d3b890a5483c9f77a000ced09
parent343e662b6224cf03fea5ebfd419c7cf990528b53 (diff)
parentbeb3f36c5dcc70beed900859d6426dc74e4bccc8 (diff)
downloadlibmts-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.h11
-rw-r--r--include/mts/MTS_IO_ICellularRadio.h68
-rw-r--r--include/mts/MTS_IO_LE910C4EURadio.h1
-rw-r--r--include/mts/MTS_IO_ME910Radio.h2
-rw-r--r--include/mts/MTS_IO_QuectelRadio.h50
-rw-r--r--include/mts/MTS_IO_TelitRadio.h49
-rw-r--r--src/MTS_IO_CellularRadio.cpp133
-rw-r--r--src/MTS_IO_ICellularRadio.cpp3
-rw-r--r--src/MTS_IO_LE910C4EURadio.cpp4
-rw-r--r--src/MTS_IO_ME910Radio.cpp32
-rw-r--r--src/MTS_IO_QuectelRadio.cpp456
-rw-r--r--src/MTS_IO_TelitRadio.cpp353
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;
+}