/* * Copyright (C) 2019 by Multi-Tech Systems * * This file is part of libmts-io. * * libmts-io is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * libmts-io is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with libmts-io. If not, see . * */ #include #include #include #include #include #include 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) { } bool TelitRadio::resetRadio(uint32_t iTimeoutMillis) { printInfo("%s| Rebooting radio", getName().c_str()); if(sendBasicCommand("AT#REBOOT") == SUCCESS) { if(iTimeoutMillis > 5000) { MTS::Thread::sleep(5000); iTimeoutMillis -= 5000; } return resetConnection(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 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()); //Always returns SUCCESS because the model should be m_sName sModel = getName(); std::string sCmd("ATI4"); std::string sResult = sendCommand(sCmd); if (sResult.find("OK") == std::string::npos) { printWarning("%s| Unable to get model from radio. Returning [%s]", getName().c_str(), getName().c_str()); return SUCCESS; } else { sModel = extractModelFromResult(sResult); if(sModel.size() == 0) { printWarning("%s| Unable to get model from radio. Returning [%s]", getName().c_str(), getName().c_str()); return SUCCESS; } } printDebug("%s| Extracted [%s] from [%s] query", getName().c_str(), sModel.c_str(), sCmd.c_str()); if(sModel != getName()) { printWarning("%s| Model identified [%s] does not match expected [%s]. Returning [%s]", getName().c_str(), sModel.c_str(), getName().c_str(), sModel.c_str()); } return SUCCESS; } ICellularRadio::CODE TelitRadio::getIccid(std::string& sIccid) { printTrace("%s| Get ICCID", getName().c_str()); sIccid = ICellularRadio::VALUE_NOT_SUPPORTED; std::string sCmd("AT#CCID"); std::string sResult = CellularRadio::sendCommand(sCmd); size_t end = sResult.find(ICellularRadio::RSP_OK); if (end == std::string::npos) { printWarning("%s| Unable to get ICCID from radio using command [%s]", getName().c_str(), sCmd.c_str()); return FAILURE; } size_t start = sResult.find("#CCID:"); if(start != std::string::npos) { start += sizeof("#CCID:"); sIccid = MTS::Text::trim(sResult.substr(start, end-start)); if(sIccid.size() == 0) { printWarning("%s| Unable to get ICCID from radio using command [%s]", getName().c_str(), sCmd.c_str()); return FAILURE; } } return SUCCESS; } ICellularRadio::CODE TelitRadio::getService(std::string& sService) { printTrace("%s| Get Service", getName().c_str()); sService = ICellularRadio::VALUE_NOT_SUPPORTED; std::string sCmd("AT#PSNT?"); std::string sResult = CellularRadio::sendCommand(sCmd); size_t end = sResult.find(ICellularRadio::RSP_OK); if (end == std::string::npos) { printWarning("%s| Unable to get Service from radio using command [%s]", getName().c_str(), sCmd.c_str()); return FAILURE; } size_t start = sResult.find(","); if(start != std::string::npos) { start += 1; //comma std::string sPsnt = MTS::Text::trim(sResult.substr(start, end-start)); int32_t iService; sscanf(sPsnt.c_str(), "%d", &iService); switch(iService) { case 0: sService = "GPRS"; break; case 1: sService = "EGPRS"; break; case 2: sService = "WCDMA"; break; case 3: sService = "HSDPA"; break; case 4: sService = "LTE"; break; default: sService = ICellularRadio::VALUE_UNKNOWN; break; } printDebug("%s| Service ID: [%d][%s]", getName().c_str(), iService, sService.c_str()); } return SUCCESS; } ICellularRadio::CODE TelitRadio::getNetwork(std::string& sNetwork) { Json::Value jData; printTrace("%s| Get Network", getName().c_str()); sNetwork = ICellularRadio::VALUE_NOT_SUPPORTED; if(getNetworkStatus(jData) == SUCCESS) { if(jData.isMember(ICellularRadio::KEY_NETWORK)) { sNetwork = jData[ICellularRadio::KEY_NETWORK].asString(); return SUCCESS; } } return FAILURE; } /* AT#RFSTS - NETWORK STATUS (GSM network) #RFSTS:,,,,,,,,,,,,, Where: - Country code and operator code(MCC, MNC) - GSM Assigned Radio Channel - Received Signal Strength Indication - Localization Area Code - Routing Area Code - Tx Power - Mobility Management state - Radio Resource state - Network Operator Mode - Cell ID - International Mobile Subscriber Identity - Operator name - Service Domain 0 - No Service 1 - CS only 2 - PS only 3 - CS+PS - Active Band 1 - GSM 850 2 - GSM 900 3 - DCS 1800 4 - PCS 1900 (WCDMA network) #RFSTS: ,,,,, RSSI>,,,,,,,,,,, ,,[,,] Where: - Country code and operator code(MCC, MNC) - UMTS Assigned Radio Channel - Active PSC(Primary Synchronization Code) - Active Ec/Io(chip energy per total wideband power in dBm) - Active RSCP (Received Signal Code Power in dBm) - Received Signal Strength Indication - Localization Area Code - Routing Area Code - Tx Power - Discontinuous reception cycle Length (cycle length in ms) - Mobility Management state - Radio Resource state - Network Operator Mode - Block Error Rate (e.g., 005 means 0.5 %) - Cell ID - International Mobile Station ID - Operator name - Service Domain (see above) - Number of Active Set (Maximum 6) UARFCN of n th active set PSC of n th active set Ec/Io of n th active Set (LTE Network) #RFSTS: - - - - - - [] - - - - - - [] - - - */ ICellularRadio::CODE TelitRadio::getNetworkStatus(Json::Value& jData) { int32_t iValue; std::string sValue; const uint32_t GSM_NETWORK_FORMAT = 14; const uint32_t WCDMA_NETWORK_FORMAT = 19; const uint32_t LTE_NETWORK_FORMAT = 16; printTrace("%s| Get Network Status", getName().c_str()); //Always get common network stats because this should never fail //This way the basic stats are always returned even if AT#RFSTS fails below getCommonNetworkStats(jData); std::string sCmd; std::string sResult; // LE910 radios have a bug where issuing AT#RFSTS with a locked SIM // will cause the radio to stop responding until a radio power cycle // Telit Support Portal Case #5069697 // LE910C1-NS is an LE910, so we stop the scan after the 0. if (getName().find("LE910") != std::string::npos) { sCmd = "AT+CPIN?"; sResult = sendCommand(sCmd); if (sResult.find("+CPIN:") == std::string::npos) { printDebug("%s| AT+CPIN? returned unexpected response: [%s][%s]", getName().c_str(), sCmd.c_str(), sResult.c_str()); printTrace("%s| Network Status:\n%s\n", getName().c_str(), jData.toStyledString().c_str()); return SUCCESS; //return SUCCESS because getCommonNetworkStats() succeeded at top of this function } if (sResult.find("SIM PIN") != std::string::npos || sResult.find("SIM PUK") != std::string::npos) { printError("%s| The SIM is locked and must first be unlocked", getName().c_str()); printTrace("%s| Network Status:\n%s\n", getName().c_str(), jData.toStyledString().c_str()); return SUCCESS; //return SUCCESS because getCommonNetworkStats() succeeded at top of this function } } sCmd = "AT#RFSTS"; sResult = sendCommand(sCmd, DEFAULT_BAIL_STRINGS, 200); if (sResult.find("#RFSTS:") == std::string::npos) { //On LTE radios without signal, this case will run because AT#RFSTS just returns "OK" printDebug("%s| Network Status command returned unexpected response: [%s][%s]", getName().c_str(), sCmd.c_str(), sResult.c_str()); printTrace("%s| Network Status:\n%s\n", getName().c_str(), jData.toStyledString().c_str()); return SUCCESS; //return SUCCESS because getCommonNetworkStats() succeeded at top of this function } size_t start = sResult.find(":") + 1; //Position right after "#RFSTS:" std::vector vParts = MTS::Text::split(MTS::Text::trim(sResult.substr(start)), ","); if (vParts.size() < 3) { printDebug("%s| Network Status command reponse is an unknown format: [%s][%s]", getName().c_str(), sCmd.c_str(), sResult.c_str()); printTrace("%s| Network Status:\n%s\n", getName().c_str(), jData.toStyledString().c_str()); return SUCCESS; //return SUCCESS because getCommonNetworkStats() succeeded at top of this function } else { //Country Code and Operator Code std::vector vPLMN = MTS::Text::split(vParts[0], ' '); if(vPLMN.size() == 2) { jData[ICellularRadio::KEY_MCC] = MTS::Text::strip(vPLMN[0], '"'); jData[ICellularRadio::KEY_MNC] = MTS::Text::strip(vPLMN[1], '"'); } jData[ICellularRadio::KEY_CHANNEL] = vParts[1]; } if (vParts.size() == GSM_NETWORK_FORMAT ) { //Parse as GSM Network Format jData[ICellularRadio::KEY_RSSIDBM] = vParts[2]; jData[ICellularRadio::KEY_LAC] = vParts[3]; jData[ICellularRadio::KEY_RAC] = vParts[4]; jData[ICellularRadio::KEY_TXPWR] = vParts[5]; jData[ICellularRadio::KEY_MM] = vParts[6]; jData[ICellularRadio::KEY_RR] = vParts[7]; jData[ICellularRadio::KEY_NOM] = vParts[8]; jData[ICellularRadio::KEY_CID] = vParts[9]; jData[ICellularRadio::KEY_IMSI] = MTS::Text::strip(vParts[10], '"'); jData[ICellularRadio::KEY_NETWORK] = MTS::Text::strip(vParts[11], '"'); if(MTS::Text::parse(iValue, vParts[12]) && convertServiceDomainToString((SERVICEDOMAIN)iValue, sValue) == SUCCESS) { jData[ICellularRadio::KEY_SD] = sValue; } if(MTS::Text::parse(iValue, vParts[13]) && convertActiveBandToString((ACTIVEBAND)iValue, sValue) == SUCCESS) { jData[ICellularRadio::KEY_ABND] = sValue; } // IN003567 ME910C1 radios have some odd behavior with regards to WCDMA. The ordering of the fields from #RFSTS are // the same as LTE up to the 16th field (for ME901C1-WW anyway). Drop into LTE parsing for ME910C1-WW. } else if((vParts.size() >= WCDMA_NETWORK_FORMAT) && (getName().find("ME910C1-WW") == std::string::npos)) { Json::Value jDebug; //Parse as WCDMA Network Format jDebug[ICellularRadio::KEY_PSC] = vParts[2]; jDebug[ICellularRadio::KEY_ECIO] = vParts[3]; jDebug[ICellularRadio::KEY_RSCP] = vParts[4]; jData[ICellularRadio::KEY_RSSIDBM] = vParts[5]; jData[ICellularRadio::KEY_LAC] = vParts[6]; jData[ICellularRadio::KEY_RAC] = vParts[7]; jDebug[ICellularRadio::KEY_TXPWR] = vParts[8]; jDebug[ICellularRadio::KEY_DRX] = vParts[9]; jDebug[ICellularRadio::KEY_MM] = vParts[10]; jDebug[ICellularRadio::KEY_RR] = vParts[11]; jDebug[ICellularRadio::KEY_NOM] = vParts[12]; if(vParts[13].size() != 0) { jDebug[ICellularRadio::KEY_BLER] = vParts[13]; } else { jDebug[ICellularRadio::KEY_BLER] = "000"; } jData[ICellularRadio::KEY_CID] = vParts[14]; jData[ICellularRadio::KEY_IMSI] = MTS::Text::strip(vParts[15], '"'); jData[ICellularRadio::KEY_NETWORK] = MTS::Text::strip(vParts[16], '"'); // Get the radio band given the channel (UARFCN) RadioBandMap radioBandMap(vParts[1], CellularRadio::ICellularRadio::VALUE_TYPE_CDMA); jData[ICellularRadio::KEY_ABND] = radioBandMap.getRadioBandName(); if(MTS::Text::parse(iValue, vParts[17]) && convertServiceDomainToString((SERVICEDOMAIN)iValue, sValue) == SUCCESS) { jDebug[ICellularRadio::KEY_SD] = sValue; } //Ignoring Active Set Values // - Number of Active Set (Maximum 6) // - UARFCN of n th active set // - PSC of n th active set // - Ec/Io of n th active Set jData[ICellularRadio::KEY_DEBUG] = jDebug; } else if(vParts.size() >= LTE_NETWORK_FORMAT) { Json::Value jDebug; //Parse as LTE Network Format // // MD: It is noticed that LTE Network format may vary depending on the firmware version: // // ,,,,,,[],,,,,,[],,, // Ex 1: #RFSTS: "310 260",2300,-98,-63,-14,AA06,,128,19,0,0501D02,"310260754792598","T-Mobile",3,4,197 // // ,,,,,,,[],,,,,,[],, // Ex 2: #RFSTS:"310 410",5780,-105,-73,-14,4603,255,,128,19,0,0000098,"310410536498694","AT&T",3,17 // #RFSTS:"311 480",1150,-96,-66,-9.0,bf35,FF,0,0,19,1,"2ED1B0E","311480148817753","Verizon",2,2,720000,10800 // #RFSTS:"310 410",2175,-120,-89,-17.5,4612,FF,0,0,19,1,"4E5E916","310410807276607","AT&T",3,4 // // Additional parameter in the second example shifts the rest of the parameters. Here we are trying to figure out // which format is currently produced based on field position which always has double quotation marks. // if (vParts[13].find("\"") != std::string::npos) { // parse the RAC and then remove it from the vector jData[ICellularRadio::KEY_RAC] = vParts[6]; vParts.erase(vParts.begin() + 6); } jDebug["rsrp"] = vParts[2]; jDebug[ICellularRadio::KEY_RSSIDBM] = vParts[3]; jDebug["rsrq"] = vParts[4]; jData["tac"] = vParts[5]; jDebug[ICellularRadio::KEY_TXPWR] = vParts[6]; jData[ICellularRadio::KEY_DRX] = vParts[7]; jDebug[ICellularRadio::KEY_MM] = vParts[8]; jDebug["rrc"] = vParts[9]; jData[ICellularRadio::KEY_CID] = MTS::Text::strip(vParts[10], '"'); jData[ICellularRadio::KEY_IMSI] = MTS::Text::strip(vParts[11], '"'); jData[ICellularRadio::KEY_NETWORK] = MTS::Text::strip(vParts[12], '"'); // Get the radio band given the channel (EARFCN) RadioBandMap radioBandMap(vParts[1], ICellularRadio::VALUE_TYPE_LTE); jData[ICellularRadio::KEY_ABND] = radioBandMap.getRadioBandName(); jData[ICellularRadio::KEY_LAC] = queryLteLac(); if(MTS::Text::parse(iValue, vParts[13]) && convertServiceDomainToString((SERVICEDOMAIN)iValue, sValue) == SUCCESS) { jDebug[ICellularRadio::KEY_SD] = sValue; } jData[ICellularRadio::KEY_DEBUG] = jDebug; } printTrace("%s| Network Status:\n%s\n", getName().c_str(), jData.toStyledString().c_str()); return SUCCESS; } ICellularRadio::CODE TelitRadio::convertSignalStrengthTodBm(const int32_t& iRssi, int32_t& iDbm) { //Telit Conversion if(iRssi < 0 || iRssi == 99) { return FAILURE; } if(iRssi == 0) { iDbm = -113; } else if(iRssi == 1) { iDbm = -111; } else if(iRssi <= 30) { //28 steps between 2 and 30 //54 dbm between 53 and 109 float stepSize = 54.0 / 28.0; iDbm = -109 + (int)(stepSize * (iRssi-2)); } else { iDbm = -51; } return SUCCESS; } ICellularRadio::CODE TelitRadio::convertdBmToSignalStrength(const int32_t& iDBm, int32_t& iRssi) { //Telit Conversion if(iDBm <= -113) { iRssi = 0; } else if(iDBm <= -111) { iRssi = 1; } else if(iDBm <= -53) { //54 dbm between -109 and -53 //28 steps between 2 and 30 float stepSize = 28.0/54.0; iRssi = ((iDBm + 109)*stepSize) + 2; } else { iRssi = 31; } return SUCCESS; } ICellularRadio::CODE TelitRadio::setMdn(const Json::Value& jArgs) { printTrace("%s| Set MDN", getName().c_str()); if(!jArgs["mdn"].isString()) { return INVALID_ARGS; } std::string sCmd("AT#SNUM=1,\""); sCmd += jArgs["mdn"].asString() + "\""; std::string sResult = sendCommand(sCmd, DEFAULT_BAIL_STRINGS, 1000); size_t end = sResult.find(ICellularRadio::RSP_OK); if (end == std::string::npos) { printWarning("%s| Unable to set MDN for radio using command [%s]", getName().c_str(), sCmd.c_str()); return FAILURE; } return SUCCESS; } bool TelitRadio::getCarrierFromFirmware(const std::string& sFirmware, std::string& sCarrier) { // Telit Radios // H.ab.zyx => 3 Main Components // "H" = Hardware -> 15 = DE910 family, 18 = CE910 family, 12 = HE910 family // "a" = Hardware version // "b" = Software Major Version // "z" = is the product type, i.e. DUAL or SC // "y" = is the carrier variant // "x" = is the firmware version // Telit will do their best to keep the carrier variant as "0" for Sprint, "1" for Aeris, "2" for Verizon, and "3" for U.S. Cellular. const uint32_t CARRIER_INDEX = 1; //y in [zyx] bool bResult = false; std::vector vParts = MTS::Text::split(sFirmware, '.'); if(vParts.size() == 3) { //CDMA firmware version notation if(vParts[0] == "15" || vParts[0] == "18") { //DE910 or CE910 -> Good good std::string sID = vParts[2]; if(sID.size() == 3) { char cId = sID[CARRIER_INDEX]; //Good good if(cId == '0') { sCarrier = ICellularRadio::VALUE_CARRIER_SPRINT; bResult = true; } else if(cId == '1') { sCarrier = ICellularRadio::VALUE_CARRIER_AERIS; bResult = true; } else if(cId == '2') { sCarrier = ICellularRadio::VALUE_CARRIER_VERIZON; bResult = true; } else if(cId == '3') { sCarrier = ICellularRadio::VALUE_CARRIER_USCELLULAR; bResult = true; } } } } return bResult; } bool TelitRadio::getHardwareVersionFromFirmware(const std::string& sFirmware, std::string& sHardware) { // Telit Radios // H.ab.zyx => 3 Main Components // "H" = Hardware -> 15 = DE910 family, 18 = CE910 family, 12 = HE910 family // "a" = Hardware version // "b" = Software Major Version // "z" = is the product type, i.e. DUAL or SC // "y" = is the carrier variant // "x" = is the firmware version // Telit will do their best to keep the carrier variant as "0" for Sprint, "1" for Aeris, and "2" for Verizon. const uint32_t HARDWARE_INDEX = 0; //a in [ab] bool bResult = false; std::vector vParts = MTS::Text::split(sFirmware, '.'); if(vParts.size() == 3) { //GSM Hardware Version if(!(vParts[0] == "15" || vParts[0] == "18")) { //Not DE910 or CE910 -> Good good std::string sVersion = vParts[1]; if(sVersion.size() == 2) { sHardware = "1."; sHardware += sVersion[HARDWARE_INDEX]; bResult = true; } } } return bResult; } ICellularRadio::CODE TelitRadio::getIsSimInserted(bool& bData) { printTrace("%s| Get SIM insertion status", getName().c_str()); std::string sCmd("AT#SIMDET?"); std::string sResult = sendCommand(sCmd); const std::string sPrefix = "#SIMDET: "; size_t start = sResult.find(sPrefix); size_t end = sResult.rfind(ICellularRadio::RSP_OK); if (end == std::string::npos) { printWarning("%s| Unable to get SIM insertion status from radio using command [%s]", getName().c_str(), sCmd.c_str()); return FAILURE; } if (start == std::string::npos) { printDebug("%s| AT#SIMDET? returned unexpected response: [%s][%s]", getName().c_str(), sCmd.c_str(), sResult.c_str()); return FAILURE; } // #SIMDET: , start += sPrefix.size(); std::vector vParts = MTS::Text::split(MTS::Text::trim(sResult.substr(start, end-start)), ','); if(vParts.size() != 2) { printWarning("%s| Unable to parse SIM insertion status from response [%s]", getName().c_str(), sResult.c_str()); return FAILURE; } if (vParts[1] == "1") { // bData = true; } else { bData = false; } return SUCCESS; } ICellularRadio::CODE TelitRadio::getSimLockAttempts(int& iAttemptsPin, int& iAttemptsPuk) { std::string sLockStatus; ICellularRadio::CODE retCode; retCode = getSimLockStatus(sLockStatus); if (retCode != SUCCESS) { printWarning("%s| Unable determine the number of SIM unlock attempts: SIM lock status is unavailable [%s]", getName().c_str(), sLockStatus.c_str()); return retCode; } return getSimLockAttempts(iAttemptsPin, iAttemptsPuk, sLockStatus); } ICellularRadio::CODE TelitRadio::getSimLockAttempts(int& iAttemptsPin, int& iAttemptsPuk, const std::string& sLockStatus) { printTrace("%s| Get SIM unlock attempts left", getName().c_str()); std::string sCmd("AT#PCT"); std::string sResult = sendCommand(sCmd, DEFAULT_BAIL_STRINGS, 500); std::string sValue; int iValue; const std::string sPrefix = "#PCT: "; size_t start = sResult.find(sPrefix); size_t end = sResult.rfind(ICellularRadio::RSP_OK); if (end == std::string::npos) { printWarning("%s| Unable to get SIM unlock attempts from radio using command [%s]", getName().c_str(), sCmd.c_str()); return FAILURE; } if (start == std::string::npos) { printDebug("%s| AT#PCT? returned unexpected response: [%s][%s]", getName().c_str(), sCmd.c_str(), sResult.c_str()); return FAILURE; } // #PCT: start += sPrefix.size(); sValue = MTS::Text::trim(sResult.substr(start, end-start)); if (!MTS::Text::parse(iValue, sValue)) { printWarning("%s| Unable to parse SIM unlock attempts from response [%s]", getName().c_str(), sResult.c_str()); return FAILURE; } if (sLockStatus == "READY" || sLockStatus == "SIM PIN") { iAttemptsPin = iValue; // Some PIN attempts left, maximum PUK attempts left iAttemptsPuk = 10; } else { iAttemptsPin = 0; // No PIN attempts left iAttemptsPuk = iValue; } return SUCCESS; } ICellularRadio::CODE TelitRadio::getSupportedCellularModes(CELLULAR_MODES &networks) { networks = CELLULAR_MODE_NA; std::set wds; if ( wdsList(wds) != SUCCESS ) { return FAILURE; } for(const auto &it : wds) { switch (it) { case 12: networks = static_cast(networks | CELLULAR_MODE_2G); break; case 22: networks = static_cast(networks | CELLULAR_MODE_3G); break; case 28: networks = static_cast(networks | CELLULAR_MODE_4G); break; case 36: networks = static_cast(networks | CELLULAR_MODE_5G); break; } } return SUCCESS; } ICellularRadio::CODE TelitRadio::wdsList(std::set &wds) { std::string sCmd("AT+WS46=?"); std::string cmdResult = sendCommand(sCmd); if (cmdResult.find("+WS46:") == std::string::npos) { printError("%s| AT+WS46=? returned unexpected response: [%s][%s]", getName().c_str(), sCmd.c_str(), cmdResult.c_str()); return FAILURE; } if (cmdResult.find('(') == std::string::npos) { printError("AT+WS46: error responce %s", cmdResult.c_str()); return FAILURE; } std::string s = MTS::Text::split(cmdResult, '(')[1]; s = MTS::Text::split(s, ')')[0]; std::vectorv = MTS::Text::split(s, ','); for(const auto &it : v) { if (it.find("-") != std::string::npos) { const std::vector &r = MTS::Text::split(it, "-"); int begin, end; if ( ! MTS::Text::parse(begin, r[0]) || ! MTS::Text::parse(end, r[1])) { printError("AT+WS46: error parsing network mode range: %s-%s", r[0].c_str(), r[1].c_str()); return FAILURE; } for (int i = begin; i<=end; ++i) { wds.insert(i); if (wds.size()>1024) break; } } else { int v; if ( ! MTS::Text::parse(v, it)) { printError("AT+WS46: error parsing network mode: %s", it.c_str()); return FAILURE; } wds.insert(v); } } if (wds.size()>1024) { printError("AT+WS46: network modes count overflow, parsing error"); return FAILURE; } return SUCCESS; } ICellularRadio::CODE TelitRadio::setCellularMode(CELLULAR_MODES networks) { std::set supportedWds; if ( wdsList(supportedWds) != SUCCESS ) { return FAILURE; } int wds = 0; // 3GPP TS 27.007 // https://portal.3gpp.org/desktopmodules/Specifications/SpecificationDetails.aspx?specificationId=1515 switch (static_cast(networks)) { case CELLULAR_MODE_2G : wds = 12; break; case CELLULAR_MODE_3G : wds = 22; break; case CELLULAR_MODE_2G | CELLULAR_MODE_3G | CELLULAR_MODE_4G : wds = 25; break; case CELLULAR_MODE_4G : wds = 28; break; case CELLULAR_MODE_2G | CELLULAR_MODE_3G : supportedWds.count(28) ? wds = 29 : wds = 25; break; case CELLULAR_MODE_2G | CELLULAR_MODE_4G : wds = 30; break; case CELLULAR_MODE_3G | CELLULAR_MODE_4G : wds = 31; break; case CELLULAR_MODE_2G | CELLULAR_MODE_3G | CELLULAR_MODE_4G | CELLULAR_MODE_5G: wds = 35; break; case CELLULAR_MODE_5G: wds = 36; break; case CELLULAR_MODE_4G | CELLULAR_MODE_5G: wds = 37; break; case CELLULAR_MODE_3G | CELLULAR_MODE_4G | CELLULAR_MODE_5G: wds = 38; break; case CELLULAR_MODE_2G | CELLULAR_MODE_4G | CELLULAR_MODE_5G: wds = 39; break; case CELLULAR_MODE_3G | CELLULAR_MODE_5G: wds = 40; break; case CELLULAR_MODE_2G | CELLULAR_MODE_3G | CELLULAR_MODE_5G: wds = 41; break; case CELLULAR_MODE_2G | CELLULAR_MODE_5G: wds = 42; break; } std::string sCmd("AT+WS46="); sCmd += std::to_string(wds); std::string cmdResult = sendCommand(sCmd); if (cmdResult.find(ICellularRadio::RSP_OK) == std::string::npos) { printError("%s| AT+WS46= returned unexpected response: [%s][%s]", getName().c_str(), sCmd.c_str(), cmdResult.c_str()); return FAILURE; } 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 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 vBailStrings{ ICellularRadio::RSP_CONNECT, ICellularRadio::RSP_ERROR }; const int dTimeout = 10000; //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 duFirstAttachTimeout = 6 * 60 * 1000; // wait for 6 minutes for the radio to attach const uint32_t duSecondAttachTimeout = 60 * 1000; // wait for 1 minute for the radio to attach const uint32_t duThirdAttachTimeout = 5 * 1000; // wait for 5 seconds for the radio to attach const uint32_t duUrcTimeout = 60 * 1000; // wait for 1 minutes for the next URC message const uint32_t timeoutMillis = 30 * 60 * 1000; // wait for 30 minutes in case if radio will send invalid message (garbage) 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 vFotaBailStrings{ sFotaUrcPrefix }; CODE rc = FAILURE; std::string sResponse; // It's now detached. Try to reconnect if (!resetConnection(duFirstAttachTimeout)) { printError("Can't connect to the radio in %d ms", (duFirstAttachTimeout)); callNextStep(stepCb, "FUMO Error: unable to obtain radio after reset"); return ERROR; } Timer timer; timer.start(); while (timer.getMillis() <= (uint64_t)timeoutMillis) { sResponse = waitResponse(vFotaBailStrings, duUrcTimeout); printTrace("Radio response: [%s]", sResponse.c_str()); if (sResponse.empty()) { // Radio detached again. Try to reconnect if (!resetConnection(duSecondAttachTimeout)) { printError("Can't connect to the radio in %d ms", (duSecondAttachTimeout)); callNextStep(stepCb, "FUMO Error: unable to obtain radio after second reset"); return ERROR; } sResponse = waitResponse(vFotaBailStrings, duUrcTimeout); 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; } } // Required to set echo to disable, so we performed resetConnection again. if (!resetConnection(duThirdAttachTimeout)) { printError("Can't connect to the radio in %d ms", (duThirdAttachTimeout)); callNextStep(stepCb, "FUMO Error: unable to reset connection to radio"); return ERROR; } 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; } ICellularRadio::CODE TelitRadio::getSelectedBandsRaw(std::string& sRawBands) { printTrace("%s| Acquiring selected bands", getName().c_str()); CODE rc; const std::string sCommand = "AT#BND?"; const std::string sLabel = "#BND: "; const int dTimeout = 1000; std::string sResult; rc = sendBasicQuery(sCommand, sLabel, sResult, dTimeout); if (rc != SUCCESS) { return rc; } std::vector vParts = MTS::Text::split(sResult, ','); uint8_t iNumBandParams = 0; if (vParts.size() > 0) { uint16_t iSelectedBands = 0; if (!isContainsSignChar(vParts[0]) && MTS::Text::parse(iSelectedBands, MTS::Text::trim(vParts[0]))) { sRawBands = MTS::Text::formatHex(iSelectedBands); // GSM bands iNumBandParams++; } else { printWarning("%s| Error during parse number from string: [%s]. Assuming that no GSM bands selected", getName().c_str(), vParts[0].c_str()); sRawBands = "ffff"; } } else { sRawBands = "ffff"; } if (vParts.size() > 1) { uint16_t iSelectedBands = 0; if (!isContainsSignChar(vParts[1]) && MTS::Text::parse(iSelectedBands, MTS::Text::trim(vParts[1]))) { sRawBands += "," + MTS::Text::formatHex(iSelectedBands); // WCDMA bands iNumBandParams++; } else { printWarning("%s| Error during parse number from string: [%s]. Assuming that no WCDMA bands selected", getName().c_str(), vParts[0].c_str()); sRawBands += ",ffff"; } } else { sRawBands += ",ffff"; } if (vParts.size() > 2) { uint64_t iSelectedBands = 0; if (!isContainsSignChar(vParts[2]) && MTS::Text::parseHex(iSelectedBands, MTS::Text::trim(vParts[2]))) { sRawBands += "," + MTS::Text::formatHex(iSelectedBands); // LTE bands iNumBandParams++; } else { printWarning("%s| Error during parse number from string: [%s]. Assuming that no LTE bands selected", getName().c_str(), vParts[0].c_str()); sRawBands += ",ffffffffffffffff"; } } else { sRawBands += ",ffffffffffffffff"; } // All other fragments - ignored for now. // Return success if at least one band param was extracted; otherwise failure return (iNumBandParams > 0) ? SUCCESS : FAILURE; } bool MTS::IO::TelitRadio::isContainsSignChar(const std::string& str) { if (str.find_first_of("+-") == std::string::npos) { return false; } return true; } std::vector TelitRadio::getSupportedPdpContextAuthTypes() const { return { VALUE_PDP_CONTEXT_AUTH_TYPE_NONE, VALUE_PDP_CONTEXT_AUTH_TYPE_PAP, VALUE_PDP_CONTEXT_AUTH_TYPE_CHAP }; } ICellularRadio::CODE TelitRadio::isPdpContextAuthSupported(bool& isSupported) { std::string sCmd("AT#PDPAUTH=?"); return isCommandSupported(sCmd, isSupported); } ICellularRadio::CODE TelitRadio::fillPdpContextAuthFields(Json::Value& jData) { CODE rc; std::string sCmd("AT#PDPAUTH?"); std::string sResult; // #PDPAUTH: 1,0 // #PDPAUTH: 2,0 // #PDPAUTH: 3,0 // #PDPAUTH: 4,1,"admin" rc = sendBasicQuery(sCmd, "", sResult, 300); if (SUCCESS != rc) { return rc; } std::vector vContexts = MTS::Text::split(sResult, "#PDPAUTH:"); std::string sContextId; for (std::string& sLine : vContexts) { sLine = MTS::Text::trim(sLine); if (sLine.empty()) { continue; } // 4,1,"admin" // [0][1] [2] auto vAuthParams = MTS::Text::split(sLine, ","); // Contains 2 elements when the authentication type is NONE, contains 3 elements in other cases if (vAuthParams.size() < 2) { printError("%s| Failed to parse PDP context authentication string [%s]", getName().c_str(), sLine.c_str()); return FAILURE; } sContextId = vAuthParams[0]; uint8_t iValue; if (! MTS::Text::parse(iValue, vAuthParams[1])) { printError("%s| Failed to parse PDP context authentication type [%s]", getName().c_str(), vAuthParams[1].c_str()); return FAILURE; } rc = convertPdpContextAuthTypeToString(static_cast(iValue), sResult); if (SUCCESS != rc) { printError("%s| Failed to convert PDP context authentication type to string [%d]", getName().c_str(), iValue); return rc; } jData[sContextId][KEY_PDP_CONTEXT_AUTH_TYPE] = sResult; if (iValue == PDP_CONTEXT_AUTH_TYPE::NONE) { continue; } if (vAuthParams.size() < 3) { printError("%s| Failed to parse PDP context authentication string [%s]", getName().c_str(), sLine.c_str()); return FAILURE; } jData[sContextId][KEY_PDP_CONTEXT_AUTH_USERNAME] = MTS::Text::trim(vAuthParams[2], '"'); } return SUCCESS; } ICellularRadio::CODE TelitRadio::setPdpContextAuth(const PdpContextInfo& pdpContext) { printTrace("%s| Setting PDP context authentication to the radio", getName().c_str()); CODE rc; std::string sCmd = "AT#PDPAUTH="; PDP_CONTEXT_AUTH_TYPE eAuthType; sCmd += pdpContext.sId + ","; rc = convertStringToPdpContextAuthType(pdpContext.sAuthType, eAuthType); if (SUCCESS != rc) { return rc; } sCmd += std::to_string(eAuthType); if (PDP_CONTEXT_AUTH_TYPE::NONE != eAuthType) { sCmd += "," + pdpContext.sUsername + "," + pdpContext.sPassword; } return sendBasicCommand(sCmd); } const std::vector& TelitRadio::getDiagCommands(bool) { // Declare as static to initialize only when used, but cache the results. const static std::vector vCommands { // Radio model and firmware: "AT+CGMI", "AT+CGMM", "AT+CGMR", "AT#SWPKGV", "AT#CFVR", // All carrier profiles that are supported: "AT#FWSWITCH=?", // Current operator profile on the radio side: "AT#FWSWITCH?", "AT+CGSN", // SIM card information: "AT#SIMDET?", "AT#CCID", "AT+CPIN?", "AT#PCT", // Operating mode of the radio: "AT+CFUN?", // Low-level network settings: "AT+WS46?", "AT#RXDIV", "AT#CALLDISA?", "AT+CEMODE?", // Data connection configuration: "AT+CGDCONT?", "AT#PDPAUTH?", // Registration and connection to the tower: "AT+CIND?", "AT+CSQ", "AT+COPS?", "AT+CREG?", "AT+CGREG?", "AT+CEREG?", "AT#RFSTS", "AT#PSNT?", "AT#MONI", // Data connection status: "AT+CGACT?", "AT+CGCONTRDP=1", "AT+CGCONTRDP=2", "AT+CGCONTRDP=3" }; return vCommands; } ICellularRadio::CODE TelitRadio::setTimeFormat() { printTrace("%s| Set standard time format", getName().c_str()); ICellularRadio::CODE rc; // Set year format in YYYY first, in case it is in YY format to get accurate year std::string sCmdCSDF("AT+CSDF=1,2"); rc = sendBasicCommand(sCmdCSDF); if (rc != SUCCESS) { printError("%s| Unable to set year format for radio using command [%s]", getName().c_str(), sCmdCSDF.c_str()); return rc; } // Set command enables the automatic time zone update std::string sCmdCTZU("AT+CTZU=1"); rc = sendBasicCommand(sCmdCTZU); if (rc != SUCCESS) { printError("%s| Unable to set automatic time zone update for radio using command [%s]", getName().c_str(), sCmdCTZU.c_str()); return rc; } return SUCCESS; }