summaryrefslogtreecommitdiff
path: root/src/MTS_IO_ME910C1NVRadio.cpp
diff options
context:
space:
mode:
authorMykyta Dorokhin <mykyta.dorokhin@globallogic.com>2018-05-08 14:05:38 +0300
committerJeff Hatch <Jeff.Hatch@multitech.com>2018-05-08 13:31:49 -0500
commitbfcef5e9d1e384cf34ebef0f7cc98858a8445827 (patch)
treebc60999188fafd1d537ce98cc4fdbfc8aff7f16f /src/MTS_IO_ME910C1NVRadio.cpp
parent1b8146c578dbea3868e16e560f5800007d104b5f (diff)
downloadlibmts-io-bfcef5e9d1e384cf34ebef0f7cc98858a8445827.tar.gz
libmts-io-bfcef5e9d1e384cf34ebef0f7cc98858a8445827.tar.bz2
libmts-io-bfcef5e9d1e384cf34ebef0f7cc98858a8445827.zip
Add FTP FOTA upgrade functionality for ME910C1-NV radios1.0.7
Signed-off-by: Jeff Hatch <Jeff.Hatch@multitech.com>
Diffstat (limited to 'src/MTS_IO_ME910C1NVRadio.cpp')
-rw-r--r--src/MTS_IO_ME910C1NVRadio.cpp581
1 files changed, 580 insertions, 1 deletions
diff --git a/src/MTS_IO_ME910C1NVRadio.cpp b/src/MTS_IO_ME910C1NVRadio.cpp
index bc2282f..e9a4874 100644
--- a/src/MTS_IO_ME910C1NVRadio.cpp
+++ b/src/MTS_IO_ME910C1NVRadio.cpp
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2017 by Multi-Tech Systems
+ * Copyright (C) 2018 by Multi-Tech Systems
*
* This file is part of libmts-io.
*
@@ -21,17 +21,32 @@
/*!
\file MTS_IO_ME910C1NVRadio.cpp
\brief A brief description
+ \date May 1, 2018
+ \author mykyta.dorokhin
A more elaborate description
*/
+#include <fstream>
#include <mts/MTS_Text.h>
#include <mts/MTS_Logger.h>
+#include <mts/MTS_Thread.h>
+#include <mts/MTS_Timer.h>
#include <mts/MTS_IO_ME910C1NVRadio.h>
using namespace MTS::IO;
const std::string ME910C1NVRadio::MODEL_NAME("ME910C1-NV");
+const std::string ME910C1NVRadio::KEY_FUMO_PDPID("pdpid"); // optional (default : "3")
+const std::string ME910C1NVRadio::KEY_FUMO_PDPTYPE("pdptype"); // optional (default : "IPV4V6")
+const std::string ME910C1NVRadio::KEY_FUMO_APN("apn"); // optional (default : "")
+const std::string ME910C1NVRadio::KEY_FUMO_ADDRESS("address");
+const std::string ME910C1NVRadio::KEY_FUMO_DIR("dir");
+const std::string ME910C1NVRadio::KEY_FUMO_FILE("file");
+const std::string ME910C1NVRadio::KEY_FUMO_USER("user");
+const std::string ME910C1NVRadio::KEY_FUMO_PASSWORD("password");
+const std::string ME910C1NVRadio::KEY_FUMO_DRYRUN("dryrun");
+
ME910C1NVRadio::ME910C1NVRadio(const std::string& sPort)
: ME910Radio(MODEL_NAME, sPort)
{
@@ -42,3 +57,567 @@ CellularRadio::CODE ME910C1NVRadio::getCarrier(std::string& sCarrier) {
sCarrier = "Verizon";
return SUCCESS;
}
+
+CellularRadio::CODE ME910C1NVRadio::doGetFirmwareNumbers(std::string &sFirmware, std::string &sFirmwareBuild) {
+ CellularRadio::CODE rc = FAILURE;
+
+ rc = getFirmware(sFirmware);
+ if (rc != SUCCESS){
+ return rc;
+ }
+
+ rc = getFirmwareBuild(sFirmwareBuild);
+ if (rc != SUCCESS){
+ return rc;
+ }
+
+ return rc;
+}
+
+CellularRadio::CODE ME910C1NVRadio::doFumoReadConfig(const Json::Value& jArgs, Json::Value &jConfig)
+{
+ CellularRadio::CODE rc = INVALID_ARGS;
+ std::string sPath;
+
+ do
+ {
+ if (!jArgs["config-file"].isString()) {
+ rc = INVALID_ARGS;
+ break;
+ }
+
+ sPath = jArgs["config-file"].asString();
+
+ std::ifstream file(sPath.c_str());
+ if (!file.is_open()) {
+ printError("Failed to open file [%s]", sPath.c_str());
+ break;
+ }
+
+ file.seekg(0, std::ios::end);
+ size_t size = file.tellg();
+ std::string buffer(size, ' ');
+ file.seekg(0);
+ file.read(&buffer[0], size);
+ file.close();
+
+ Json::Features features = Json::Features::strictMode();
+ Json::Reader reader(features);
+ if (!reader.parse(buffer, jConfig)) {
+ printError("Error parsing FOTA configuration file");
+ break;
+ }
+
+ //
+ // set default values if missing
+ //
+ if (!jConfig.isMember(KEY_FUMO_PDPID)) {
+ jConfig[KEY_FUMO_PDPID] = std::string("3");
+ }
+
+ if (!jConfig.isMember(KEY_FUMO_PDPTYPE)) {
+ jConfig[KEY_FUMO_PDPTYPE] = std::string("IPV4V6");
+ }
+
+ if (!jConfig.isMember(KEY_FUMO_APN)) {
+ jConfig[KEY_FUMO_APN] = std::string("");
+ }
+
+ //
+ // validate
+ //
+ if (!jConfig[KEY_FUMO_PDPID].isString()) {
+ printError("Error loading FOTA configuration: PDP context id is not set");
+ break;
+ }
+
+ if (jConfig[KEY_FUMO_PDPID].asString().empty()) {
+ printError("Error loading FOTA configuration: context id is empty");
+ break;
+ }
+
+ if (!jConfig[KEY_FUMO_PDPTYPE].isString()) {
+ printError("Error loading FOTA configuration: PDP type is not set");
+ break;
+ }
+
+ if (jConfig[KEY_FUMO_PDPTYPE].asString().empty()) {
+ printError("Error loading FOTA configuration: PDP type is empty");
+ break;
+ }
+
+ // Note : allow empty APN
+ if (!jConfig[KEY_FUMO_APN].isString()) {
+ printError("Error loading FOTA configuration: APN is not set");
+ break;
+ }
+
+ if (!jConfig[KEY_FUMO_ADDRESS].isString()) {
+ printError("Error loading FOTA configuration: address is not set");
+ break;
+ }
+
+ if (jConfig[KEY_FUMO_ADDRESS].asString().empty()) {
+ printError("Error loading FOTA configuration: address is empty");
+ break;
+ }
+
+ // Note: allow empty dir
+ if (!jConfig[KEY_FUMO_DIR].isString()) {
+ printError("Error loading FOTA configuration: directory is not set");
+ break;
+ }
+
+ if (!jConfig[KEY_FUMO_FILE].isString()) {
+ printError("Error loading FOTA configuration: filename is not set");
+ break;
+ }
+
+ if (jConfig[KEY_FUMO_FILE].asString().empty()) {
+ printError("Error loading FOTA configuration: filename is empty");
+ break;
+ }
+
+ // Note: allow empty username/password
+ if (!jConfig[KEY_FUMO_USER].isString()) {
+ printError("Error loading FOTA configuration: username is not set");
+ break;
+ }
+
+ if (!jConfig[KEY_FUMO_PASSWORD].isString()) {
+ printError("Error loading FOTA configuration: password is not set");
+ break;
+ }
+
+ rc = SUCCESS;
+ }
+ while(0);
+
+ return rc;
+}
+
+CellularRadio::CODE ME910C1NVRadio::doFumoSetup(const Json::Value &jConfig, UpdateCb& stepCb)
+{
+ CellularRadio::CODE rc = FAILURE;
+ std::string sCmd;
+
+ std::string sContextId = jConfig[KEY_FUMO_PDPID].asString();
+ std::string sApn = jConfig[KEY_FUMO_APN].asString();
+ std::string sPdpType = jConfig[KEY_FUMO_PDPTYPE].asString();
+
+
+ do
+ {
+ //
+ // Execution command is used to activate or deactivate either the GSM
+ // context or the specified PDP context.
+ //
+ // AT#SGACT=<cid>,<stat>[,<userId>,<pwd>]
+ //
+ sCmd = "AT#SGACT=" + sContextId + ",0";
+ rc = CellularRadio::sendBasicCommand(sCmd);
+ if (rc != SUCCESS) {
+ if(stepCb) {
+ stepCb(Json::Value("FUMO Error: Failed to deactivate PDP context"));
+ }
+ break;
+ }
+
+ //
+ // Read current Firmware numbers (let it be after AT#SGACT not to confuse users)
+ //
+ rc = doGetFirmwareNumbers(m_sFw, m_sFwBuild);
+ if (rc != SUCCESS) {
+ if(stepCb) {
+ stepCb(Json::Value("FUMO Error: Failed to obtain current firmware version"));
+ }
+ break;
+ }
+
+ //
+ // Set command specifies PDP context parameter values for a PDP context identified by
+ // the (local) context identification parameter <cid>.
+ //
+ // AT+CGDCONT= [<cid>[,<PDP_type>[,<APN>[,<PDP_addr>[,<d_comp>[,<h_comp>[,<pd1>[,...[,pdN]]]]]]]]]
+ //
+ sCmd = "AT+CGDCONT=" + sContextId + ",\"" + sPdpType + "\"";
+ if (!sApn.empty()) {
+ sCmd += ",\"" + sApn + "\"";
+ }
+ rc = CellularRadio::sendBasicCommand(sCmd, 1000);
+ if (rc != SUCCESS) {
+ if(stepCb) {
+ stepCb(Json::Value("FUMO Error: Failed to setup PDP context"));
+ }
+ break;
+ }
+
+ //
+ // Set command sets the socket configuration parameters.
+ //
+ // AT#SCFG==<connId>,<cid>,<pktSz>,<maxTo>,<connTo>,<txTo>
+ // <connId> - socket connection identifier
+ // <cid> - PDP context identifier
+ // <pktSz> - packet size to be used by the TCP/UDP/IP stack for data sending.
+ // <maxTo> - exchange timeout (or socket inactivity timeout); if there’s no
+ // data exchange within this timeout period the connection is closed (timeout value in seconds).
+ // <connTo> - connection timeout; if we can’t establish a connection to the
+ // remote within this timeout period, an error is raised timeout value in hundreds of milliseconds.
+ // <txTo> - data sending timeout; after this period data are sent also if they’re
+ // less than max packet size (timeout value in hundreds of milliseconds).
+ //
+ sCmd = "AT#SCFG=1," + sContextId + ",300,90,600,50";
+ rc = CellularRadio::sendBasicCommand(sCmd);
+ if (rc != SUCCESS) {
+ if(stepCb) {
+ stepCb(Json::Value("FUMO Error: Failed to set connection configuration parameters"));
+ }
+ break;
+ }
+
+ //
+ // Activate PDP context
+ //
+ sCmd = "AT#SGACT=" + sContextId + ",1";
+ rc = CellularRadio::sendBasicCommand(sCmd, 60 * 1000);
+ if (rc != SUCCESS) {
+ if(stepCb) {
+ stepCb(Json::Value("FUMO Error: Failed to activate PDP context"));
+ }
+ break;
+ }
+ }
+ while (0);
+
+ return rc;
+}
+
+CellularRadio::CODE ME910C1NVRadio::doFumoFtp(const Json::Value &jConfig, UpdateCb& stepCb)
+{
+ CellularRadio::CODE rc = FAILURE;
+ std::string sCmd;
+ std::string sResult;
+
+ //
+ // Set command sets the time-out used when opening either the FTP control
+ // channel or the FTP traffic channel.
+ //
+ // AT#FTPTO= [<tout>]
+ // <tout> - time-out in 100 ms units
+ //
+ rc = CellularRadio::sendBasicCommand("AT#FTPTO=2400");
+ if (rc != SUCCESS) {
+ if(stepCb) {
+ stepCb(Json::Value("FUMO Error: Failed to setup connection timeout"));
+ }
+ return rc;
+ }
+
+ //
+ // Execution command opens an FTP connection toward the FTP server.
+ //
+ // AT#FTPOPEN=[<server:port>,<username>,<password>[,<mode>]]
+ //
+ sCmd = "AT#FTPOPEN=";
+ sCmd += "\"" + jConfig[KEY_FUMO_ADDRESS].asString() + "\",";
+ sCmd += "\"" + jConfig[KEY_FUMO_USER].asString() + "\",";
+ sCmd += "\"" + jConfig[KEY_FUMO_PASSWORD].asString() + "\",1";
+ rc = CellularRadio::sendBasicCommand(sCmd, 60 * 1000);
+ if (rc != SUCCESS) {
+ if(stepCb) {
+ stepCb(Json::Value("FUMO Error: Failed to open connection"));
+ }
+ return rc;
+ }
+
+ if (stepCb) {
+ stepCb(Json::Value("FUMO Info: connection opened"));
+ }
+
+ do
+ {
+ //
+ // Set command, issued during an FTP connection, sets the file transfer type.
+ //
+ // AT#FTPTYPE=[<type>]
+ // <type> - file transfer type:
+ // 0 - binary
+ // 1 - ascii
+ //
+ rc = CellularRadio::sendBasicCommand("AT#FTPTYPE=0", 1000);
+ if (rc != SUCCESS) {
+ if(stepCb) {
+ stepCb(Json::Value("FUMO Error: failed to set file transfer type"));
+ }
+ break;
+ }
+
+ //
+ // Execution command, issued during an FTP connection, changes the
+ // working directory on FTP server.
+ //
+ // AT#FTPCWD=[<dirname>]
+ //
+ sCmd = "AT#FTPCWD=\"/";
+ if (!jConfig[KEY_FUMO_DIR].asString().empty()) {
+ sCmd += jConfig[KEY_FUMO_DIR].asString() + "/";
+ }
+ sCmd += "\"";
+ rc = CellularRadio::sendBasicCommand(sCmd, 60 * 1000);
+ if (rc != SUCCESS) {
+ if(stepCb) {
+ stepCb(Json::Value("FUMO Error: failed to change working directory on the server"));
+ }
+ break;
+ }
+
+ if (stepCb) {
+ stepCb(Json::Value("FUMO Info: downloading the firmware"));
+ }
+
+ //
+ // Start FTP transfer
+ //
+ sCmd = "AT#FTPGETOTA=";
+ sCmd += "\"" + jConfig[KEY_FUMO_FILE].asString() + "\",1,1";
+ CellularRadio::sendBasicCommand(sCmd);
+
+ //
+ // Noticed that after successful AT#FTPGETOTA the radio resets the connection.
+ // and the response code (OK, ERROR.. ) not always reach the host. Therefore
+ // we send the AT#FTPGETOTA with relatively small timeout and then poll with AT
+ // until we get valid response. After that, using AT#FTPMSG we can check the
+ // result of the last FTP command (which is AT#FTPGETOTA in our case).
+ MTS::Timer oTimer;
+
+ oTimer.start();
+
+ while (oTimer.getSeconds() < (30 * 60)) // 30 min
+ {
+ MTS::Thread::sleep(5000);
+
+ rc = CellularRadio::sendBasicCommand("AT");
+ if (rc == SUCCESS) {
+ break;
+ }
+
+ CellularRadio::resetConnection(1);
+ }
+ oTimer.stop();
+
+ if (rc != SUCCESS) {
+ if(stepCb) {
+ stepCb(Json::Value("FUMO Error: unable to obtain radio after reset"));
+ }
+ break;
+ }
+
+ //
+ // Now check the FTP status
+ //
+ std::string sResult = sendCommand("AT#FTPMSG");
+
+ printTrace("RADIO| AT#FTPMSG result [%s]", sResult.c_str());
+
+ if (sResult.find(RSP_OK) == std::string::npos) {
+ rc = FAILURE;
+ if(stepCb) {
+ stepCb(Json::Value("FUMO Error: failed to download the firmware file"));
+ }
+ break;
+ }
+ if (sResult.find("#FTPMSG: 550") != std::string::npos) {
+ // FTP(550) : File not found
+ rc = FAILURE;
+ if(stepCb) {
+ stepCb(Json::Value("FUMO Error: file not found"));
+ }
+ break;
+ }
+ if (sResult.find("#FTPMSG: 226") == std::string::npos) {
+ // FTP(226) : Successfully transferred
+ rc = FAILURE;
+ if(stepCb) {
+ stepCb(Json::Value("FUMO Error: failed to download the firmware file"));
+ }
+ break;
+ }
+ }
+ while (0);
+
+ //
+ // Execution command closes an FTP connection.
+ //
+ // AT#FTPCLOSE
+ //
+ CellularRadio::CODE rcclose = CellularRadio::sendBasicCommand("AT#FTPCLOSE", 60 * 1000);
+ if (rcclose != SUCCESS && rc == SUCCESS) {
+ if(stepCb) {
+ // Only one "FUMO Error" message should be sent
+ stepCb(Json::Value("FUMO Error: Failed to close FTP connection"));
+ }
+ rc = rcclose;
+ }
+
+ if (rc == SUCCESS) {
+ if(stepCb) {
+ stepCb(Json::Value("FUMO Info: firmware downloaded successfully"));
+ }
+ }
+
+ return rc;
+}
+
+CellularRadio::CODE ME910C1NVRadio::doFumoCleanup(const Json::Value &jConfig, UpdateCb& stepCb)
+{
+ CellularRadio::CODE rc = FAILURE;
+ std::string sCmd;
+
+ std::string sContextId = jConfig[KEY_FUMO_PDPID].asString();
+
+ //
+ // Deactivate PDP context
+ //
+ sCmd = "AT#SGACT=" + sContextId + ",0";
+ rc = CellularRadio::sendBasicCommand(sCmd, 10000);
+ if (rc != SUCCESS) {
+ if(stepCb) {
+ stepCb(Json::Value("FUMO Error: Failed to deactivate PDP context"));
+ }
+ }
+ return rc;
+}
+
+CellularRadio::CODE ME910C1NVRadio::doFumoApplyFirmware(const Json::Value &jConfig, UpdateCb& stepCb)
+{
+ CellularRadio::CODE rc = FAILURE;
+
+ if (jConfig.isMember(KEY_FUMO_DRYRUN)) {
+ if(stepCb) {
+ stepCb(Json::Value("FUMO Info: applying the radio firmware"));
+ }
+ return SUCCESS;
+ }
+
+ rc = CellularRadio::sendBasicCommand("AT#OTAUP=0", 10000);
+ if (rc != SUCCESS) {
+ if(stepCb) {
+ stepCb(Json::Value("FUMO Error: failed to apply the firmware"));
+ }
+ return rc;
+ }
+
+ if(stepCb) {
+ stepCb(Json::Value("FUMO Info: applying the radio firmware"));
+ }
+
+ return rc;
+}
+
+CellularRadio::CODE ME910C1NVRadio::doFumoWaitNewFirmware(const Json::Value &jConfig, UpdateCb& stepCb)
+{
+ std::string sFirmware;
+ std::string sFirmwareBuild;
+ CellularRadio::CODE rc = FAILURE;
+
+ if (jConfig.isMember(KEY_FUMO_DRYRUN)) {
+ if(stepCb) {
+ stepCb(Json::Value("FUMO done: radio firmware applied successfully"));
+ }
+ return SUCCESS;
+ }
+
+ // The radio is expected to send "#OTAEV: Module Upgraded To New Fw" unsolicited message
+ // on success. However, for some reason, we do not see this message.
+
+ MTS::Timer oTimer;
+ oTimer.start();
+
+ while (oTimer.getSeconds() < (5 * 60)) { // 5 minutes
+
+ MTS::Thread::sleep(10000);
+
+ if (doGetFirmwareNumbers(sFirmware, sFirmwareBuild) != SUCCESS) {
+ // The radio is probably unavailable
+ CellularRadio::resetConnection(100);
+ continue;
+ }
+
+ if (sFirmware == m_sFw && sFirmwareBuild == m_sFwBuild) {
+ // Have the same firmware. The radio resets several time
+ // before the firmware is actually get upgraded. So keep polling.
+ continue;
+ }
+
+ // The firmware numbers have changed
+ rc = SUCCESS;
+ break;
+ }
+ oTimer.stop();
+
+ if (rc == SUCCESS) {
+ if(stepCb) {
+ stepCb(Json::Value("FUMO done: radio firmware applied successfully"));
+ }
+ }
+ else {
+ if(stepCb) {
+ stepCb(Json::Value("FUMO error: radio firmware has not been updated"));
+ }
+ }
+
+ return rc;
+}
+
+
+CellularRadio::CODE ME910C1NVRadio::doFumoPerform(const Json::Value &jConfig, UpdateCb& stepCb)
+{
+ CellularRadio::CODE rc = FAILURE;
+
+ UpdateCb dummyCb;
+
+ // Set the PDP context for the FOTA
+ rc = doFumoSetup(jConfig, stepCb);
+ if (rc != SUCCESS) {
+ return rc;
+ }
+
+ // Download FW over FTP
+ rc = doFumoFtp(jConfig, stepCb);
+ if (rc != SUCCESS) {
+ doFumoCleanup(jConfig, dummyCb);
+ return rc;
+ }
+
+ // Clean up before applying the FW file
+ rc = doFumoCleanup(jConfig, stepCb);
+ if (rc != SUCCESS) {
+ return rc;
+ }
+
+ // Apply the FW file
+ rc = doFumoApplyFirmware(jConfig, stepCb);
+ if (rc != SUCCESS) {
+ return rc;
+ }
+
+ rc = doFumoWaitNewFirmware(jConfig, stepCb);
+
+ return rc;
+}
+
+CellularRadio::CODE ME910C1NVRadio::updateFumo(const Json::Value& jArgs, UpdateCb& stepCb)
+{
+ Json::Value jConfig(Json::objectValue);
+ CellularRadio::CODE rc = FAILURE;
+
+ rc = doFumoReadConfig(jArgs, jConfig);
+ if (rc != SUCCESS) {
+ if(stepCb) {
+ stepCb(Json::Value("FUMO Error: bad configuration parameters"));
+ }
+ return rc;
+ }
+
+ return doFumoPerform(jConfig, stepCb);
+}