/* * 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 "mts/MTS_IO_QuectelRadio.h" #include #include #include using namespace MTS::IO; QuectelRadio::QuectelRadio(const std::string& sName, const std::string& sRadioPort) : CellularRadio (sName, sRadioPort) { } bool QuectelRadio::resetRadio(uint32_t iTimeoutMillis) { printInfo("%s| Rebooting radio", getName().c_str()); if(sendBasicCommand("AT+CFUN=1,1") == SUCCESS) { if(iTimeoutMillis > 5000) { MTS::Thread::sleep(5000); iTimeoutMillis -= 5000; } return resetConnection(iTimeoutMillis); } return false; } ICellularRadio::CODE QuectelRadio::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("AT+GMM"); 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 QuectelRadio::getIccid(std::string& sIccid) { printTrace("%s| Get ICCID", getName().c_str()); sIccid = ICellularRadio::VALUE_NOT_SUPPORTED; // AT+QCCID execution can take up to 300ms according to the datasheet. Setting timeout to 500ms just for sure. std::string sCmd("AT+QCCID"); std::string sResult = sendCommand(sCmd, DEFAULT_BAIL_STRINGS, 500); 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("+QCCID:"); if(start != std::string::npos) { start += sizeof("+QCCID:"); 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 QuectelRadio::getService(std::string& sService) { printTrace("%s| Get Service", getName().c_str()); sService = ICellularRadio::VALUE_NOT_SUPPORTED; std::string sCmd("AT+COPS?"); std::string sResult = 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(":") + 1; //Position right after "+COPS:" std::vector vParts = MTS::Text::split(MTS::Text::trim(sResult.substr(start, end-start)), ','); int32_t iAccessTechnology; // +COPS: [,[,][,]] if (vParts.size() < 4 || !MTS::Text::parse(iAccessTechnology, vParts[3])) { printWarning("%s| Unable to get Service from radio using command [%s]", getName().c_str(), sCmd.c_str()); return FAILURE; } switch(iAccessTechnology) { case 0 : sService = "GPRS" ; break; // GSM case 2 : sService = "WCDMA" ; break; // UTRAN case 3 : sService = "EGPRS" ; break; // GSM W/EGPRS case 4 : sService = "HSDPA" ; break; // UTRAN W/HSDPA case 5 : sService = "WCDMA" ; break; // UTRAN W/HSUPA case 6 : sService = "HSDPA" ; break; // UTRAN W/HSDPA and HSUPA case 7 : sService = "LTE" ; break; // E-UTRAN case 100 : sService = "CDMA" ; break; // CDMA default: sService = ICellularRadio::VALUE_UNKNOWN; break; } printDebug("%s| Service ID: [%d][%s]", getName().c_str(), iAccessTechnology, sService.c_str()); return SUCCESS; } ICellularRadio::CODE QuectelRadio::getNetwork(std::string& sNetwork) { /* * TODO: Refactor using MccMncTable once it'll be corrected. * * The proper way to determine the current network is to do that * by MCC and MNC fetched from the `getNetworkStatus` and `AT+QENG` command. * By using MCC and MNC from `AT+QENG` we can fetch the name of the network * reported by a currently connected base station even if the SIM card is * not installed or if we are currently working is a roaming mode. * * Until MccMncTable implementation is not fixed, we are using the name * of a currently selected operator (AT+COPS). */ printTrace("%s| Get Network", getName().c_str()); sNetwork = ICellularRadio::VALUE_NOT_SUPPORTED; std::string sCmd("AT+COPS?"); std::string sResult = sendCommand(sCmd); size_t end = sResult.find(ICellularRadio::RSP_OK); if (end == std::string::npos) { printWarning("%s| Unable to get network name from radio using command [%s]", getName().c_str(), sCmd.c_str()); return FAILURE; } // +COPS: [, , ,] // +COPS: vParts[0],vParts[1],vParts[2],vParts[3] size_t start = sResult.find(":") + 1; //Position right after "+COPS:" std::vector vParts = MTS::Text::split(MTS::Text::trim(sResult.substr(start)), ","); if(vParts.size() > 3) { const std::string sValue = vParts[2]; // +COPS: 0,0,"CHN-UNICOM UNICOM",7 // ^start ^end // +COPS: 0,0,"AT&T",7 // ^st ^end size_t start = sValue.find("\"") + 1; size_t end = sValue.find_first_of(" \"", start); sNetwork = sValue.substr(start, end-start); } else { sNetwork = ""; // Not connected to any network } return SUCCESS; } /* AT+QENG="servingcell" - Query the information of serving cells (GSM network) +QENG:"servingscell",,"GSM",,,,,,,,,,,,,,,,,,,,,,,, (WCDMA network) +QENG:"servingcell",,"WCDMA",,,,,,,,,,,,,, (LTE Network) +QENG:"servingcell",,"LTE",,,,,,,,,,,,,,, The following modes are NOT currently handled: - TD-SCDMA mode; - CDMA mode; - HDR mode; - SRLTE mode. In the case of TD-SCDMA mode: +QENG:"servingscell",,"TDSCDMA",,,,,,,, In the case of CDMA mode or CDMA+HDR mode: +QENG:"servingscell",,"CDMA",,,,,,,, [+QENG:"servingscell",,"HDR",,,,,,,,] In the case of SRLTE mode: +QENG:"servingscell",,"CDMA",,,,,,,, +QENG:"servingcell",,"LTE",,,,,,,,,,,,,, */ CellularRadio::CODE QuectelRadio::getNetworkStatus(Json::Value& jData) { const std::string RAT_GSM = "GSM"; const std::string RAT_WCDMA = "WCDMA"; const std::string RAT_LTE = "LTE"; ACTIVEBAND abnd; SERVICEDOMAIN sd; std::string sValue; std::string sRat; // Radio Access Technology which is currently used 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+QENG fails below getCommonNetworkStats(jData); // IMSI is not provided by AT+QENG. Fetch it separately to keep the same interface if (getImsi(sValue) == SUCCESS) { jData[ICellularRadio::KEY_IMSI] = sValue; } // Network Name is not explicitly provided by AT+QENG. Fetch it separately to keep the same interface // TODO: Replace with lookup by MCC and MNC once MccMncTable is fixed. if (getNetwork(sValue) == SUCCESS) { jData[ICellularRadio::KEY_NETWORK] = sValue; } std::string sCmd; std::string sResult; sCmd = "AT+QENG=\"servingcell\""; sResult = sendCommand(sCmd, DEFAULT_BAIL_STRINGS, 200); if (sResult.find("+QENG: \"servingcell\"") == std::string::npos) { 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 "+QENG:" size_t end = sResult.rfind(ICellularRadio::RSP_OK); std::vector vParts = MTS::Text::split(MTS::Text::trim(sResult.substr(start, end-start)), ","); Json::Value jDebug; Json::Value jQuectelDebug; 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 { // UE state and Access technology, Quectel-specific information jQuectelDebug["state"] = vParts[1]; sRat = MTS::Text::trim(vParts[2], '"'); jQuectelDebug["rat"] = sRat; } // +QENG:"servingscell",,"GSM",,,,,,,,,,,,,,,,,,,,,,,, // +QENG: [0], [1], [2], [3], [4], [5], [6], [7], [8], [9], [10], [11], [12], [13],[14],[15], [16], [17],[18],[19], [20], [21], [22], [23], [24], [25], [26] if (sRat == RAT_GSM) { //Parse as GSM Network Format jData[ICellularRadio::KEY_MCC] = vParts[3]; jData[ICellularRadio::KEY_MNC] = vParts[4]; jData[ICellularRadio::KEY_LAC] = vParts[5]; jData[ICellularRadio::KEY_CID] = vParts[6]; jQuectelDebug["bsic"] = vParts[7]; jData[ICellularRadio::KEY_CHANNEL] = vParts[8]; if (convertToActiveBand(vParts[9], abnd) == SUCCESS && convertActiveBandToString(abnd, sValue) == SUCCESS) { jData[ICellularRadio::KEY_ABND] = sValue; } jData[ICellularRadio::KEY_RSSIDBM] = vParts[10]; // Values already negative. No need to substract 111 as stated in a datasheet jData[ICellularRadio::KEY_TXPWR] = vParts[11]; jQuectelDebug["rla"] = vParts[12]; jQuectelDebug["drx"] = vParts[13]; jQuectelDebug["c1"] = vParts[14]; jQuectelDebug["c2"] = vParts[15]; jQuectelDebug["gprs"] = vParts[16]; jQuectelDebug["tch"] = vParts[17]; jQuectelDebug["ts"] = vParts[18]; jQuectelDebug["ta"] = vParts[19]; jQuectelDebug["maio"] = vParts[20]; jQuectelDebug["hsn"] = vParts[21]; jQuectelDebug["rxlevsub"] = vParts[22]; jQuectelDebug["rxlevfull"] = vParts[23]; jQuectelDebug["rxqualsub"] = vParts[24]; jQuectelDebug["rxqualfull"] = vParts[25]; jQuectelDebug["voicecodec"] = vParts[26]; // Service Domain is not provided by AT+QENG. Fetch it separately to keep the same interface if (getServiceDomain(sd) == SUCCESS && convertServiceDomainToString(sd, sValue) == SUCCESS) { jData[ICellularRadio::KEY_SD] = sValue; } // The following fields can NOT be fetched for Quectel in GSM mode: RAC, MM, RR, NOM jData["quectelDebug"] = jQuectelDebug; } // +QENG:"servingcell",,"WCDMA",,,,,,,,,,,,,, // +QENG: [0], [1], [2], [3], [4], [5], [6], [7], [8], [9], [10], [11], [12],[13], [14], [15], [16] else if(sRat == RAT_WCDMA) { //Parse as WCDMA Network Format jData[ICellularRadio::KEY_MCC] = vParts[3]; jData[ICellularRadio::KEY_MNC] = vParts[4]; jData[ICellularRadio::KEY_LAC] = vParts[5]; jData[ICellularRadio::KEY_CID] = vParts[6]; jData[ICellularRadio::KEY_CHANNEL] = vParts[7]; jDebug[ICellularRadio::KEY_PSC] = vParts[8]; jData[ICellularRadio::KEY_RAC] = vParts[9]; jDebug[ICellularRadio::KEY_RSCP] = vParts[10]; jDebug[ICellularRadio::KEY_ECIO] = vParts[11]; jQuectelDebug["phych"] = vParts[12]; jQuectelDebug["sf"] = vParts[13]; jQuectelDebug["slot"] = vParts[14]; jQuectelDebug["speechCode"] = vParts[15]; jQuectelDebug["comMod"] = vParts[16]; // The following fields can NOT be fetched for Quectel in WCDMA mode: TXPWR, DRX, MM, RR, NOM, BLER // RSSI is not provided by AT+QENG in WCDMA mode. It was filled above by the getCommonNetworkStats // Service Domain is not provided by AT+QENG. Fetch it separately to keep the same interface if (getServiceDomain(sd) == SUCCESS && convertServiceDomainToString(sd, sValue) == SUCCESS) { jDebug[ICellularRadio::KEY_SD] = sValue; } // BLER is not provided by AT+QENG. Set to constant jDebug[ICellularRadio::KEY_BLER] = "000"; // Get the radio band given the channel (UARFCN) RadioBandMap radioBandMap(vParts[7], ICellularRadio::VALUE_TYPE_CDMA); jData[ICellularRadio::KEY_ABND] = radioBandMap.getRadioBandName(); jData["quectelDebug"] = jQuectelDebug; jData[ICellularRadio::KEY_DEBUG] = jDebug; } // +QENG:"servingcell",,"LTE",,,,,,,,,,,,,,, // +QENG: [0], [1], [2], [3], [4], [5], [6], [7], [8], [9], [10], [11], [12], [13], [14], [15], [16], [17] else if(sRat == RAT_LTE) { //Parse as LTE Network Format jQuectelDebug["isTdd"] = vParts[3]; jData[ICellularRadio::KEY_MCC] = vParts[4]; jData[ICellularRadio::KEY_MNC] = vParts[5]; jData[ICellularRadio::KEY_CID] = vParts[6]; jQuectelDebug["pcid"] = vParts[7]; jData[ICellularRadio::KEY_CHANNEL] = vParts[8]; jQuectelDebug["freqBandInd"] = vParts[9]; jQuectelDebug["ulBandwidth"] = vParts[10]; jQuectelDebug["dlBandwidth"] = vParts[11]; jData["tac"] = vParts[12]; jDebug["rsrp"] = vParts[13]; jDebug["rsrq"] = vParts[14]; jDebug[ICellularRadio::KEY_RSSIDBM] = vParts[15]; jQuectelDebug["sinr"] = vParts[16]; jQuectelDebug["srxlev"] = vParts[17]; // Get the radio band given the channel (EARFCN) RadioBandMap radioBandMap(vParts[8], ICellularRadio::VALUE_TYPE_LTE); jData[ICellularRadio::KEY_ABND] = radioBandMap.getRadioBandName(); // Service Domain is not provided by AT+QENG. Fetch it separately to keep the same interface if (getServiceDomain(sd) == SUCCESS && convertServiceDomainToString(sd, sValue) == SUCCESS) { jDebug[ICellularRadio::KEY_SD] = sValue; } // LAC is not provided by AT+QENG in WCDMA mode. Use another command instead jData[ICellularRadio::KEY_LAC] = queryLteLac(); jData["quectelDebug"] = jQuectelDebug; jData[ICellularRadio::KEY_DEBUG] = jDebug; } printTrace("%s| Network Status:\n%s\n", getName().c_str(), jData.toStyledString().c_str()); return SUCCESS; } ICellularRadio::CODE QuectelRadio::convertSignalStrengthTodBm(const int32_t& iRssi, int32_t& iDbm) { int dbmSteps, minValue, maxValue, rssiOffset; int rawDbm; if(iRssi >= 0 && iRssi < 99) { // normal scaling dbmSteps = 2; minValue = -113; maxValue = -51; rssiOffset = 0; } else if(iRssi >= 100 && iRssi < 199) { // TD-SCDMA scaling dbmSteps = 1; minValue = -116; maxValue = -25; rssiOffset = 100; } else { return FAILURE; // invalid, not known or not detectable } rawDbm = minValue + ((iRssi - rssiOffset) * dbmSteps); iDbm = std::min(maxValue, rawDbm); return SUCCESS; } ICellularRadio::CODE QuectelRadio::convertdBmToSignalStrength(const int32_t& iDBm, int32_t& iRssi) { //Quectel Conversion FOR NORMAL SCALING const int dbmSteps = 2; const int minValue = -113; const int rssiOffset = 0; if (iDBm < -113) { iRssi = 0; } else if (iDBm > -51) { iRssi = 31; } else { iRssi = ((iDBm - minValue) / dbmSteps) + rssiOffset; } return SUCCESS; } ICellularRadio::CODE QuectelRadio::setMdn(const Json::Value& jArgs) { printTrace("%s| Set MDN", getName().c_str()); return NOT_APPLICABLE; } ICellularRadio::CODE QuectelRadio::startOmaDm(ICellularRadio::UpdateCb& stepCb) { printTrace("%s| Start OMA DM procedure", getName().c_str()); const int32_t iTimeoutOk = 3 * 1000; // 3 seconds const int32_t iTimeoutStart = 5 * 1000; // 5 seconds const int32_t iTimeoutEnd = 1 * 60 * 1000; // 1 minute const std::string sOdmStarted = "DM Start"; const std::string sOdmFinished = "DM End"; const std::vector vOdmStartedStrings{ sOdmStarted }; const std::vector vOdmFinishedStrings{ sOdmFinished }; CODE eCode; do { // Send command and expect "OK" in iTimeoutOk milliseconds eCode = sendBasicCommand("AT+QODM=\"dme\",2,\"ui\"", 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")); } break; } // Wait for the "Start" response std::string sResponse = sendCommand("", vOdmStartedStrings, iTimeoutStart, 0x00); printDebug("%s| Radio returned: [%s]", getName().c_str(), sResponse.c_str()); 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")); } 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")); } // Wait for the "End" response sResponse = sendCommand("", vOdmFinishedStrings, iTimeoutEnd, 0x00); printDebug("%s| Radio returned: [%s]", getName().c_str(), sResponse.c_str()); 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")); } eCode = FAILURE; break; } // 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")); } eCode = SUCCESS; } while (false); return eCode; } ICellularRadio::CODE QuectelRadio::getServiceDomain(ICellularRadio::SERVICEDOMAIN& sd) { printTrace("%s| Get Service Domain", getName().c_str()); std::string sCmd("AT+QCFG=\"servicedomain\""); std::string sResult = sendCommand(sCmd); size_t end = sResult.find(ICellularRadio::RSP_OK); if (end == std::string::npos) { printWarning("%s| Unable to get service domain using command [%s]", getName().c_str(), sCmd.c_str()); return FAILURE; } // +QCFG: "servicedomain", size_t start = sResult.find(",") + 1; // Position right after comma std::string sServiceDomain = MTS::Text::trim(sResult.substr(start, end-start)); int iValue = -1; if (!MTS::Text::parse(iValue, sServiceDomain)) { printWarning("%s| Failed to parse service domain from command output [%s]", getName().c_str(), sCmd.c_str()); return FAILURE; } switch (iValue) { case 0: sd = SERVICEDOMAIN::CS_ONLY; break; case 1: sd = SERVICEDOMAIN::PS_ONLY; break; case 2: sd = SERVICEDOMAIN::CSPS; break; default: return FAILURE; // Unknown } return SUCCESS; } ICellularRadio::CODE QuectelRadio::getIsSimInserted(bool& bData) { printTrace("%s| Get SIM insertion status", getName().c_str()); // AT+QSIMSTAT? execution can take up to 300ms according to the datasheet. Setting timeout to 500ms just for sure. std::string sCmd("AT+QSIMSTAT?"); std::string sResult = sendCommand(sCmd, DEFAULT_BAIL_STRINGS, 500); const std::string sPrefix = "+QSIMSTAT: "; 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+QSIMSTAT? returned unexpected response: [%s][%s]", getName().c_str(), sCmd.c_str(), sResult.c_str()); return FAILURE; } // +QSIMSTAT: , 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") { // Inserted bData = true; } else { // Removed or Unknown, before (U)SIM initialization bData = false; } return SUCCESS; } ICellularRadio::CODE QuectelRadio::getSimLockAttempts(int& iAttemptsPin, int& iAttemptsPuk) { printTrace("%s| Get SIM unlock attempts left", getName().c_str()); // AT+QPINC execution can take more time that expected. Set timeout to 2s just to be sure. std::string sCmd("AT+QPINC=\"SC\""); std::string sResult = sendCommand(sCmd, DEFAULT_BAIL_STRINGS, 2000); const std::string sPrefix = "+QPINC: \"SC\","; 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+QPINC returned unexpected response: [%s][%s]", getName().c_str(), sCmd.c_str(), sResult.c_str()); return FAILURE; } // +QPINC: ,, // [x] ,[0] ,[1] 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 unlock attempts left from response [%s]", getName().c_str(), sResult.c_str()); return FAILURE; } if (!MTS::Text::parse(iAttemptsPin, vParts[0])) { printWarning("%s| Unable to parse SIM PIM unlock attempts from response [%s]", getName().c_str(), sResult.c_str()); return FAILURE; } if (!MTS::Text::parse(iAttemptsPuk, vParts[1])) { printWarning("%s| Unable to parse SIM PUK unlock attempts from response [%s]", getName().c_str(), sResult.c_str()); return FAILURE; } return SUCCESS; } ICellularRadio::CODE QuectelRadio::convertToActiveBand(const std::string& sQuectelBand, ICellularRadio::ACTIVEBAND& band) { int iQuectelBand = -1; if (!MTS::Text::parse(iQuectelBand, sQuectelBand)) { return FAILURE; // probably "-", other band } switch (iQuectelBand) { case 0: band = ACTIVEBAND::DCS_1800; break; case 1: band = ACTIVEBAND::PCS_1900; break; default: return FAILURE; // actually, this case should never happen } return SUCCESS; }