/* * 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_CellularRadio.h" #include #include #include #include #include #include #include #include using namespace MTS::IO; namespace { typedef struct { const char *name; int32_t low; int32_t high; } *pNameRangeMap, nameRangeMap; const unsigned int NUM_GSM_BANDS = 7; const unsigned int NUM_WCDMA_BANDS = 6; const unsigned int NUM_LTE_BANDS = 42; // http://niviuk.free.fr/gsm_band.php const nameRangeMap GSMband[] = { {"GSM 450", 259, 293}, {"GSM 480", 306, 340}, {"GSM 750", 438, 511}, {"GSM 850", 128, 251}, {"GSM 900 P", 1, 124}, {"GSM 900 E/R", 955, 1023}, {"GSM DCS 1800/1900", 512, 885}, }; // http://niviuk.free.fr/umts_band.php const nameRangeMap WCDMAband[] = { {"BAND I", 10592, 10838}, {"BAND II", 9662, 9938}, {"BAND III", 1162, 1513}, {"BAND IV", 1537, 1738}, {"BAND V", 4357, 4458}, {"BAND VI", 4387, 4413} }; // http://niviuk.free.fr/lte_band.php const nameRangeMap EULTRAband[] = { {"EUTRAN BAND1", 0, 599}, {"EUTRAN BAND2", 600, 1199}, {"EUTRAN BAND3", 1200, 1949}, {"EUTRAN BAND4", 1950, 2399}, {"EUTRAN BAND5", 2400, 2649}, {"EUTRAN BAND6", 2650, 2749}, {"EUTRAN BAND7", 2750, 3449}, {"EUTRAN BAND8", 3450, 3799}, {"EUTRAN BAND9", 3800, 4149}, {"EUTRAN BAND10", 4150, 4749}, {"EUTRAN BAND11", 4750, 4999}, {"EUTRAN BAND12", 5000, 5179}, {"EUTRAN BAND13", 5180, 5279}, {"EUTRAN BAND14", 5280, 5379}, {"EUTRAN BAND17", 5730, 5849}, {"EUTRAN BAND18", 5850, 5999}, {"EUTRAN BAND19", 6000, 6149}, {"EUTRAN BAND20", 6150, 6449}, {"EUTRAN BAND21", 6450, 6525}, {"EUTRAN BAND22", 6600, 7399}, {"EUTRAN BAND23", 7500, 7699}, {"EUTRAN BAND24", 7700, 8039}, {"EUTRAN BAND25", 8040, 8689}, {"EUTRAN BAND26", 8690, 9039}, {"EUTRAN BAND27", 9040, 9209}, {"EUTRAN BAND28", 9210, 9659}, {"EUTRAN BAND29", 9660, 9769}, {"EUTRAN BAND30", 9770, 9869}, {"EUTRAN BAND31", 9870, 9919}, {"EUTRAN BAND32", 9920, 10359}, {"EUTRAN BAND33", 36000, 36199}, {"EUTRAN BAND34", 36200, 36349}, {"EUTRAN BAND35", 36350, 36949}, {"EUTRAN BAND36", 36950, 37549}, {"EUTRAN BAND37", 37550, 37749}, {"EUTRAN BAND38", 37750, 38249}, {"EUTRAN BAND39", 38250, 38649}, {"EUTRAN BAND40", 38650, 39649}, {"EUTRAN BAND41", 39650, 41589}, {"EUTRAN BAND42", 41590, 43589}, {"EUTRAN BAND43", 43590, 45589}, {"EUTRAN BAND44", 45590, 46589}, {"EUTRAN BAND45", 46590, 46789}, {"EUTRAN BAND46", 46790, 54539}, {"EUTRAN BAND47", 54540, 55239}, {"EUTRAN BAND48", 55240, 56739}, {"EUTRAN BAND49", 56740, 58239}, {"EUTRAN BAND50", 58240, 59089}, {"EUTRAN BAND51", 59090, 59139}, {"EUTRAN BAND52", 59140, 60139}, {"EUTRAN BAND53", 60140, 60254}, {"EUTRAN BAND65", 65536, 66435}, {"EUTRAN BAND66", 66436, 67335}, {"EUTRAN BAND67", 67336, 67535}, {"EUTRAN BAND68", 67536, 67835}, {"EUTRAN BAND69", 67836, 68335}, {"EUTRAN BAND70", 68336, 68585}, {"EUTRAN BAND71", 68586, 68935}, {"EUTRAN BAND72", 68936, 68985}, {"EUTRAN BAND73", 68986, 69035}, {"EUTRAN BAND74", 69036, 69465}, {"EUTRAN BAND75", 69466, 70315}, {"EUTRAN BAND76", 70316, 70365}, {"EUTRAN BAND85", 70366, 70545}, {"EUTRAN BAND87", 70546, 70595}, {"EUTRAN BAND88", 70596, 70645}, {"EUTRAN BAND103", 70646, 70655} }; } CellularRadio::CellularRadio(const std::string& sName, const std::string& sRadioPort) : m_sName(sName) , m_sRadioPort(sRadioPort) , m_bEchoEnabled(false) , m_bEnableEchoOnClose(false) { m_apIo.reset(new MTS::IO::SerialConnection( MTS::IO::SerialConnection::Builder(m_sRadioPort) .baudRate(115200) .useLockFile() .build())); } CellularRadio::~CellularRadio() { shutdown(); m_apIo.reset(); } bool CellularRadio::initialize(uint32_t iTimeoutMillis) { if(!m_apIo->open(iTimeoutMillis)) { printError("%s| Failed to open radio port [%s]", m_sName.c_str(), m_sRadioPort.c_str()); return false; } bool bEnabled; ICellularRadio::CODE eCode = getEcho(bEnabled); if(eCode == SUCCESS && bEnabled) { printDebug("%s| Disabling 'echo'", m_sName.c_str()); setEcho(false); m_bEnableEchoOnClose = true; } return true; } bool CellularRadio::resetConnection(uint32_t iTimeoutMillis) { //Close Current Connection if(!m_apIo.isNull()) { m_apIo->close(); } m_apIo.reset(new MTS::IO::SerialConnection( MTS::IO::SerialConnection::Builder(m_sRadioPort) .baudRate(115200) .useLockFile() .build())); //Try to obtain the device port over the given period of time MTS::Timer oTimer; oTimer.start(); uint64_t iCurrentTime = 0; while(iCurrentTime < iTimeoutMillis) { if(!m_apIo->open(iTimeoutMillis - iCurrentTime)) { printWarning("%s| Failed to re-open radio port [%s]", m_sName.c_str(), m_sRadioPort.c_str()); } else { printInfo("%s| Successfully re-opened radio port [%s]", m_sName.c_str(), m_sRadioPort.c_str()); printDebug("%s| Recovering 'echo' after connection reset", m_sName.c_str()); // see CellularRadio::initialize if (setEcho(m_bEchoEnabled) != SUCCESS) { printWarning("%s| Failed to recover 'echo' after connection reset", m_sName.c_str()); } break; } ::usleep(500000); //500 millis iCurrentTime = oTimer.getMillis(); } oTimer.stop(); return !m_apIo->isClosed(); } void CellularRadio::shutdown() { if(!m_apIo.isNull()) { if(m_bEnableEchoOnClose) { printDebug("%s| Enabling 'echo'", m_sName.c_str()); setEcho(true); m_bEnableEchoOnClose = false; } m_apIo->close(); } } const std::string& CellularRadio::getName() const { return m_sName; } ICellularRadio::CODE CellularRadio::getFirmware(std::string& sFirmware) { printTrace("%s| Get Firmware", m_sName.c_str()); sFirmware = ICellularRadio::VALUE_NOT_SUPPORTED; std::string sCmd("AT+CGMR"); 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]", m_sName.c_str(), sCmd.c_str()); return FAILURE; } sFirmware = MTS::Text::trim(sResult.substr(0, pos)); if(sFirmware.size() == 0) { printWarning("%s| Unable to get firmware from radio using command [%s]", m_sName.c_str(), sCmd.c_str()); return FAILURE; } m_sFirmware = sFirmware; return SUCCESS; } ICellularRadio::CODE CellularRadio::getFirmwareBuild(std::string& sFirmwareBuild) { sFirmwareBuild = ICellularRadio::VALUE_NOT_SUPPORTED; 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; if(m_sFirmware.size() == 0) { getFirmware(m_sFirmware); } if(getHardwareVersionFromFirmware(m_sFirmware, sHardware)) { return SUCCESS; } return FAILURE; } ICellularRadio::CODE CellularRadio::getManufacturer(std::string& sManufacturer) { printTrace("%s| Get Manufacturer", m_sName.c_str()); sManufacturer = ICellularRadio::VALUE_NOT_SUPPORTED; std::string sCmd("AT+CGMI"); std::string sResult = sendCommand(sCmd); size_t pos = sResult.find(ICellularRadio::RSP_OK); if (pos == std::string::npos) { printWarning("%s| Unable to get manufacturer from radio using command [%s]", m_sName.c_str(), sCmd.c_str()); return FAILURE; } sManufacturer = MTS::Text::trim(sResult.substr(0, pos)); if(sManufacturer.size() == 0) { printWarning("%s| Unable to get manufacturer from radio using command [%s]", m_sName.c_str(), sCmd.c_str()); return FAILURE; } return SUCCESS; } ICellularRadio::CODE CellularRadio::getImei(std::string& sImei) { printTrace("%s| Get IMEI", m_sName.c_str()); sImei = ICellularRadio::VALUE_NOT_SUPPORTED; // AT+CGSN execution can take up to 300ms according to the Quectel datasheet. Setting timeout to 500ms just for sure. std::string sCmd("AT+CGSN"); std::string sResult = sendCommand(sCmd, DEFAULT_BAIL_STRINGS, 500); size_t pos = sResult.find(ICellularRadio::RSP_OK); if (pos == std::string::npos) { printWarning("%s| Unable to get IMEI from radio using command [%s]", m_sName.c_str(), sCmd.c_str()); return FAILURE; } sImei = MTS::Text::trim(sResult.substr(0, pos)); if(sImei.size() == 0) { printWarning("%s| Unable to get IMEI from radio using command [%s]", m_sName.c_str(), sCmd.c_str()); return FAILURE; } return SUCCESS; } ICellularRadio::CODE CellularRadio::getMeid(std::string& sMeid) { printTrace("%s| Get MEID", m_sName.c_str()); return getImei(sMeid); } ICellularRadio::CODE CellularRadio::getImsi(std::string& sImsi) { printTrace("%s| Get IMSI", m_sName.c_str()); sImsi = ICellularRadio::VALUE_NOT_SUPPORTED; // AT+CIMI execution can take up to 300ms according to the Quectel datasheet. Setting timeout to 500ms just for sure. std::string sCmd("AT+CIMI"); std::string sResult = sendCommand(sCmd, DEFAULT_BAIL_STRINGS, 500); size_t pos = sResult.find(ICellularRadio::RSP_OK); if (pos == std::string::npos) { printWarning("%s| Unable to get IMSI from radio using command [%s]", m_sName.c_str(), sCmd.c_str()); return FAILURE; } sImsi = MTS::Text::trim(sResult.substr(0, pos)); if(sImsi.size() == 0) { printWarning("%s| Unable to get IMSI from radio using command [%s]", m_sName.c_str(), sCmd.c_str()); return FAILURE; } return SUCCESS; } ICellularRadio::CODE CellularRadio::getSimStatus(std::string& sSimStatus) { printTrace("%s| Get SIM Status", getName().c_str()); sSimStatus = ICellularRadio::VALUE_UNKNOWN; return FAILURE; } ICellularRadio::CODE CellularRadio::getSimStatusSummary(Json::Value& jData) { bool bIsSimInserted = false; bool bIsSimLocked = true; int iAttemptsPin = 0; int iAttemptsPuk = 0; std::string sSimLockStatus; ICellularRadio::CODE retCode; do { retCode = getIsSimInserted(bIsSimInserted); // Some radio modems do not have separate commands for the SIM presence check if (retCode == NOT_APPLICABLE) { retCode = SUCCESS; bIsSimInserted = true; } if (retCode != SUCCESS) { break; } if (!bIsSimInserted) { // There is not much left to do. Return one field only. jData[KEY_IS_SIM_INSERTED] = bIsSimInserted; break; } // The following code assumes that the SIM card is inserted retCode = getSimLockStatus(sSimLockStatus); if (retCode != SUCCESS) { /* IN:4033: * * On some devices #SIMDET reports "inserted" but +CPIN? returns ERROR when there is * no SIM card in the slot. It's also the case when only plastic holder is inserted * instead of the SIM itself. * * Interpret this error as "SIM card not detected" for such cases. */ jData[KEY_IS_SIM_INSERTED] = false; retCode = SUCCESS; break; } bIsSimLocked = (sSimLockStatus != "READY"); // SIM PIN, SIM PUK or other values retCode = getSimLockAttempts(iAttemptsPin, iAttemptsPuk); if (retCode != SUCCESS) { break; } // Everything fetched successfully. Populate the jData object jData[KEY_IS_SIM_INSERTED] = bIsSimInserted; jData[KEY_IS_SIM_LOCKED] = bIsSimLocked; jData[KEY_SIM_LOCK_STATUS] = sSimLockStatus; jData[KEY_ATTEMPTS_PIN] = iAttemptsPin; jData[KEY_ATTEMPTS_PUK] = iAttemptsPuk; } while (false); return retCode; } ICellularRadio::CODE CellularRadio::getLac(std::string& sLac) { Json::Value jData; printTrace("%s| Get LAC", m_sName.c_str()); sLac = ICellularRadio::VALUE_NOT_SUPPORTED; if(getNetworkStatus(jData) == SUCCESS) { if(jData.isMember(ICellularRadio::KEY_LAC)) { sLac = jData[ICellularRadio::KEY_LAC].asString(); return SUCCESS; } } return FAILURE; } ICellularRadio::CODE CellularRadio::getMdn(std::string& sMdn) { printTrace("%s| Get MDN", m_sName.c_str()); sMdn = ICellularRadio::VALUE_NOT_SUPPORTED; // AT+CNUM execution can take up to 300ms according to the Quectel datasheet. Setting timeout to 500ms just for sure. std::string sCmd("AT+CNUM"); 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 MDN from radio using command [%s]", m_sName.c_str(), sCmd.c_str()); return FAILURE; } size_t start = sResult.find("CNUM:"); if(start != std::string::npos) { start += sizeof("CNUM:"); std::vector vParts = MTS::Text::split(sResult.substr(start, end - start), ','); if(vParts.size() < 3) { printWarning("%s| Unable to parse MDN from response [%s]", m_sName.c_str(), sResult.c_str()); return FAILURE; } sMdn = MTS::Text::strip(vParts[1], '"'); if(sMdn.size() == 0) { printWarning("%s| Unable to get MDN from radio using command [%s]. MDN may not be set.", m_sName.c_str(), sCmd.c_str()); } } else { sMdn = ""; printWarning("%s| Unable to get MDN from radio using command [%s]. MDN may not be set.", m_sName.c_str(), sCmd.c_str()); } return SUCCESS; } ICellularRadio::CODE CellularRadio::getMsid(std::string& sMsid) { printTrace("%s| Get MSID", m_sName.c_str()); sMsid = ""; std::string sImsi; if(getImsi(sImsi) == SUCCESS) { if(sImsi.size() >= 10) { sMsid = sImsi.substr(sImsi.size() - 10); printTrace("IMSI: [%s] MEID [%s]", sImsi.c_str(), sMsid.c_str()); return SUCCESS; } } printWarning("%s| Unable to get MSID from radio", m_sName.c_str()); return FAILURE; } ICellularRadio::CODE CellularRadio::getType(std::string& sType) { printTrace("%s| Get Type", m_sName.c_str()); sType = ICellularRadio::VALUE_NOT_SUPPORTED; return FAILURE; } ICellularRadio::CODE CellularRadio::getCarrier(std::string& sCarrier) { printTrace("%s| Get Carrier", m_sName.c_str()); CODE rc = SUCCESS; std::string t_sCarrier; do { if (m_sCarrier == "") { rc = getCarrierFromSimSpn(t_sCarrier); if (rc == SUCCESS) { m_sCarrier = t_sCarrier; break; } rc = getCarrierFromSimMccMnc(t_sCarrier); if (rc == SUCCESS) { m_sCarrier = t_sCarrier; break; } } } while (false); sCarrier = m_sCarrier; return rc; } ICellularRadio::CODE CellularRadio::getCarrierFromSimMccMnc(std::string& sCarrier) { printTrace("%s| Get Carrier from SIM MCC/MNC", m_sName.c_str()); std::string sMcc; std::string sMnc; CODE rc; rc = getSimMccMnc(sMcc, sMnc); if (rc == SUCCESS) { Json::Value jLookup = MccMncTable::getInstance()->lookup(sMcc, sMnc); printTrace("%s| MCC-MNC Lookup: [%s][%s][%s]", m_sName.c_str(), sMcc.c_str(), sMnc.c_str(), jLookup.toStyledString().c_str()); if (jLookup.isMember(ICellularRadio::KEY_CARRIER)) { sCarrier = jLookup[ICellularRadio::KEY_CARRIER].asString(); } else { printWarning("%s| MCC-MNC Lookup does not contain the carrier", m_sName.c_str()); return FAILURE; } } else { printWarning("%s| SIM did no contain MCC or MNC", m_sName.c_str()); return rc; } return SUCCESS; } ICellularRadio::CODE CellularRadio::getCarrierFromSimSpn(std::string& sCarrier) { printTrace("%s| Get Carrier from SIM SPN", m_sName.c_str()); const int iEfspnId = 0x6F46; const uint8_t iOffsetHigh = 1; const uint8_t iOffsetLow = 0; const uint8_t iNumBytes = 16; CODE rc; std::string sEFspnContent; rc = simAccessReadBinary(iEfspnId, iOffsetLow, iOffsetHigh, iNumBytes, sEFspnContent); if (rc != SUCCESS) { printError("%s| Failed to determine the service provider name", m_sName.c_str()); return rc; } if (sEFspnContent.length() != 32) { printError("%s| Invalid length of the service provider name: expected [32], actual: [%d]", m_sName.c_str(), sEFspnContent.length()); return FAILURE; } uint8_t iSpnPart; for (size_t i = 0; i < sEFspnContent.length(); i += 2) { std::string sPart = sEFspnContent.substr(i, 2); // parse hex to unsigned byte if (!MTS::Text::parseHex(iSpnPart, sPart)) { printError("%s| Unexpected SIM EFspn content: [%s]", m_sName.c_str(), sEFspnContent.c_str()); return FAILURE; } // skip 0xFF bytes if (iSpnPart == 0xFF) { continue; } sCarrier.push_back(iSpnPart); } /** * Example of radio response when SIM card does not contain the service provider name: * Raw response from the radio: [ * +CRSM: 144,0,FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF * OK * ] */ if (sCarrier.empty()) { printError("%s| SIM EFspn does not contain the service provider name", m_sName.c_str()); return FAILURE; } return SUCCESS; } ICellularRadio::CODE CellularRadio::getTower(std::string& sTower) { Json::Value jData; printTrace("%s| Get Tower", m_sName.c_str()); sTower = ICellularRadio::VALUE_NOT_SUPPORTED; if(getNetworkStatus(jData) == SUCCESS) { if(jData.isMember(ICellularRadio::KEY_CID)) { sTower = jData[ICellularRadio::KEY_CID].asString(); return SUCCESS; } } return FAILURE; } ICellularRadio::CODE CellularRadio::getTime(std::string& sDate, std::string& sTime, std::string& sTimeZone) { Json::Value jData; printTrace("%s| Get Time", m_sName.c_str()); sDate = ""; sTime = ""; sTimeZone = ""; CODE rc; std::vector vTimeParts; std::string sSign = "+"; rc = setTimeFormat(); if (rc != SUCCESS) { printWarning("%s| Unable to set Time Parameters for radio [%s]", m_sName.c_str()); return rc; } std::string sCmd("AT+CCLK?"); std::string sResult = sendCommand(sCmd); size_t end = sResult.find(ICellularRadio::RSP_OK); if (end == std::string::npos) { printWarning("%s| Unable to get Time from radio using command [%s]", m_sName.c_str(), sCmd.c_str()); return FAILURE; } size_t start = sResult.find("CCLK: "); if (start != std::string::npos) { start += sizeof("CCLK: "); std::string sValue = MTS::Text::trim(sResult.substr(start, end - start)); sValue = MTS::Text::strip(sValue, '"'); std::vector vParts = MTS::Text::split(sValue, ','); if (vParts.size() != 2) { printWarning("%s| Unable to parse Date from response [%s]", m_sName.c_str(), sResult.c_str()); return FAILURE; } std::vector vDateParts = MTS::Text::split(vParts[0], '/'); if (vDateParts.size() != 3) { printWarning("%s| Unable to parse Date parts from response [%s]", m_sName.c_str(), sResult.c_str()); return FAILURE; } // The format is YYYY-MM-DD sDate = vDateParts[0] + "-" + vDateParts[1] + "-" + vDateParts[2]; sTime = (vParts[1]); //E.g. 20:39:34-16. Split time and zone with + or - sign. size_t psign = sTime.find("+"); size_t nsign = sTime.find("-"); if (psign != std::string::npos) { vTimeParts = MTS::Text::split(vParts[1], '+'); } else if (nsign != std::string::npos) { vTimeParts = MTS::Text::split(vParts[1], '-'); sSign = "-"; } else { sSign = ""; } if (vTimeParts.size() != 2) { printWarning("%s| Unable to parse Time Zone from response [%s], size %d", m_sName.c_str(), sResult.c_str(),vTimeParts.size()); return FAILURE; } //Get local time sTime = (vTimeParts[0]); int32_t iZoneUnits = 0; //the difference, expressed in quarters of an hour, between the local time and GMT if (!MTS::Text::parse(iZoneUnits, vTimeParts[1])) { printWarning("%s| Unable to parse Time Zone units from response [%s], size %d", m_sName.c_str(), sResult.c_str(),vTimeParts.size()); return FAILURE; } int32_t iZone = iZoneUnits/4; //Divide by 4 to get hours difference int32_t iZonePartial = (iZoneUnits % 4) * 15; //Remainder in minutes std::stringstream ss; ss << sSign << iZone; if (iZonePartial != 0) { ss << ":" << iZonePartial; } sTimeZone = ss.str(); return SUCCESS; } else { printWarning("%s| Unable to parse Time from radio using command [%s]", m_sName.c_str(), sCmd.c_str()); } return FAILURE; } ICellularRadio::CODE CellularRadio::getRoaming(bool& bRoaming) { Json::Value jData; printTrace("%s| Get Roaming", m_sName.c_str()); bRoaming = false; REGISTRATION eReg; if(getRegistration(eReg) == SUCCESS) { bRoaming = (eReg == ROAMING); return SUCCESS; } return FAILURE; } ICellularRadio::CODE CellularRadio::getSignalStrength(int32_t& rssi) { printTrace("%s| Get Signal Strength", m_sName.c_str()); // AT+CSQ execution can take up to 300ms according to the Quectel datasheet. Setting timeout to 500ms just for sure. std::string sCmd("AT+CSQ"); std::string sResult = sendCommand(sCmd, DEFAULT_BAIL_STRINGS, 500); if (sResult.find("+CSQ: ") == std::string::npos) { printDebug("%s| Signal Strength command returned unexpected response: [%s]", m_sName.c_str(), sResult.c_str()); return FAILURE; } size_t start = sResult.find(':'); size_t stop = sResult.find(',', start); if(start == std::string::npos || stop == std::string::npos) { printDebug("%s| Signal Strength command returned malformed response: [%s]", m_sName.c_str(), sResult.c_str()); return FAILURE; } std::string signal = sResult.substr(start + 2, stop - start - 2); sscanf(signal.c_str(), "%d", &rssi); printDebug("%s| Signal Strength: [%d]", m_sName.c_str(), rssi); return SUCCESS; } ICellularRadio::CODE CellularRadio::getModemLocation(std::string&) { printTrace("%s|CellularRadio getModemLocation - not supported", m_sName.c_str()); return FAILURE; } ICellularRadio::CODE CellularRadio::getEcho(bool& bEnabled) { printTrace("%s| Echo Test", m_sName.c_str()); std::string sResult = sendCommand("AT"); if(sResult.size() == 0) { return NO_RESPONSE; } if(sResult.find("AT") != std::string::npos) { bEnabled = true; } else { bEnabled = false; } m_bEchoEnabled = bEnabled; return SUCCESS; } ICellularRadio::CODE CellularRadio::setEcho(bool bEnabled) { ICellularRadio::CODE eCode = FAILURE; if(bEnabled) { eCode = sendBasicCommand("ATE1"); m_bEchoEnabled = (eCode == SUCCESS ) ? true : m_bEchoEnabled; } else { eCode = sendBasicCommand("ATE0"); m_bEchoEnabled = (eCode == SUCCESS ) ? false : m_bEchoEnabled; } return eCode; } ICellularRadio::CODE CellularRadio::getStaticInformation(Json::Value& jData) { printTrace("%s| Get Static Information", m_sName.c_str()); printTrace("%s| Static Information:\n%s\n", m_sName.c_str(), jData.toStyledString().c_str()); return FAILURE; } // Get the LAC for the LTE radio that's not in the #RFSTS or +QENG response std::string CellularRadio::queryLteLac() { std::string CGREGstring; std::string originalCGREG; std::string result; CGREGstring = queryCGREGstring(); if (CGREGstring == ICellularRadio::RSP_ERROR) { originalCGREG = "0"; } else { originalCGREG = CGREGstring.at(CGREGstring.find(",") - 1); //Position right before first comma ("+CGREG: 0,1") } // Temporarily set CGREG=2 to get more info setCGREG("2"); CGREGstring = queryCGREGstring(); if (CGREGstring == ICellularRadio::RSP_ERROR) { result = ICellularRadio::VALUE_UNKNOWN; } else { size_t start = CGREGstring.find(":") + 1; //Position right after "+CGREG:" std::vector vParts = MTS::Text::split(MTS::Text::trim(CGREGstring.substr(start)), ","); if(vParts.size() < 3) { result = ICellularRadio::VALUE_UNAVAILABLE; } else { result = MTS::Text::strip(vParts[2], '"'); } } setCGREG(originalCGREG); return result; } void CellularRadio::setCGREG(std::string value) { std::string sCmd("AT+CGREG=" + value); std::string cmdResult(sendCommand(sCmd)); if (cmdResult.find("OK") == std::string::npos) { printDebug("%s| AT+CGREG=%s returned unexpected response: [%s][%s]", m_sName.c_str(), value.c_str(), sCmd.c_str(), cmdResult.c_str()); } } std::string CellularRadio::queryCGREGstring() { std::string sCmd("AT+CGREG?"); std::string cmdResult(sendCommand(sCmd)); if (cmdResult.find("+CGREG:") == std::string::npos) { printDebug("%s| AT+CGREG? returned unexpected response: [%s][%s]", m_sName.c_str(), sCmd.c_str(), cmdResult.c_str()); return ICellularRadio::RSP_ERROR; } return cmdResult; } void CellularRadio::getCommonNetworkStats(Json::Value& jData) { bool bRoaming = false; if(getRoaming(bRoaming) == SUCCESS) { jData[ICellularRadio::KEY_ROAMING] = bRoaming; } int32_t iRssi; if(getSignalStrength(iRssi) == SUCCESS) { jData[ICellularRadio::KEY_RSSI] = iRssi; int32_t dBm; if(!jData.isMember(ICellularRadio::KEY_RSSIDBM) && convertSignalStrengthTodBm(iRssi, dBm) == SUCCESS) { //Add RSSI in dBm format jData[ICellularRadio::KEY_RSSIDBM] = MTS::Text::format(dBm); } } std::string sService; if(getService(sService) == SUCCESS) { jData[ICellularRadio::KEY_SERVICE] = sService; } std::string sDate, sTime, sTimeZone; if(getTime(sDate, sTime, sTimeZone) == SUCCESS) { jData[ICellularRadio::KEY_DATETIME] = sDate + " " + sTime + " GMT" + sTimeZone; } std::string sNetworkReg; REGISTRATION eReg; if (getRegistration(eReg) == SUCCESS) { if (convertRegistrationToString(eReg, sNetworkReg) == SUCCESS) { jData[ICellularRadio::KEY_NETWORK_REG] = sNetworkReg; } } std::string sCurrentCellMode; CELLULAR_MODES eModes; if (getCellularMode(eModes) == SUCCESS) { if (convertCellModesToString(eModes, sCurrentCellMode) == SUCCESS) { jData[ICellularRadio::KEY_CELL_MODE] = sCurrentCellMode; } } } ICellularRadio::CODE CellularRadio::getSimLockStatus(std::string& sData) { printTrace("%s| Get SIM lock status", m_sName.c_str()); // SIM card may introduce a delay to AT+CPIN? execution. Setting timeout to 1s as set in PPP chat patches. std::string sCmd("AT+CPIN?"); std::string sResult = sendCommand(sCmd, DEFAULT_BAIL_STRINGS, 1000); const std::string sPrefix = "+CPIN: "; size_t start = sResult.find(sPrefix); size_t end = sResult.rfind(ICellularRadio::RSP_OK); if (start == std::string::npos || end == std::string::npos) { printWarning("%s| Unable to get SIM lock status from radio using command [%s]", m_sName.c_str(), sCmd.c_str()); return FAILURE; } start += sPrefix.size(); sData = MTS::Text::trim(sResult.substr(start, end-start)); if(sData.size() == 0) { printWarning("%s| Unable to get SIM lock status from radio using command [%s]", m_sName.c_str(), sCmd.c_str()); return FAILURE; } return SUCCESS; } void CellularRadio::initMipProfile(Json::Value& jData) { jData[ICellularRadio::KEY_MIP_ID] = 0; jData[ICellularRadio::KEY_MIP_ENABLED] = false; jData[ICellularRadio::KEY_MIP_NAI] = ICellularRadio::VALUE_UNKNOWN; jData[ICellularRadio::KEY_MIP_HOMEADDRESS] = ICellularRadio::VALUE_UNKNOWN; jData[ICellularRadio::KEY_MIP_PRIMARYHA] = ICellularRadio::VALUE_UNKNOWN; jData[ICellularRadio::KEY_MIP_SECONDARYHA] = ICellularRadio::VALUE_UNKNOWN; jData[ICellularRadio::KEY_MIP_MNAAASPI] = ICellularRadio::VALUE_UNKNOWN; jData[ICellularRadio::KEY_MIP_MNHASPI] = ICellularRadio::VALUE_UNKNOWN; jData[ICellularRadio::KEY_MIP_MNAAASS] = false; jData[ICellularRadio::KEY_MIP_MNHASS] = false; } ICellularRadio::REGISTRATION CellularRadio::parseRegResponse(std::string sResult) { size_t start = sResult.find(','); size_t stop = sResult.find(' ', start); std::string sRegStat = sResult.substr(start + 1, stop - start - 1); int32_t value; sscanf(sRegStat.c_str(), "%d", &value); return (ICellularRadio::REGISTRATION)value; } ICellularRadio::CODE CellularRadio::getRegistration(REGISTRATION& eRegistration, const std::string& sType) { std::string sCmd = "AT+" + sType + "?"; std::string sResp = "+" + sType + ": "; std::string sResult = sendCommand(sCmd, DEFAULT_BAIL_STRINGS, 5000); if (sResult.find(sResp) == std::string::npos) { if(sResult.size() == 0) { printDebug("%s| Registration command returned no response", m_sName.c_str()); return NO_RESPONSE; } printDebug("%s| Registration command returned unexpected response: [%s]", m_sName.c_str(), sResult.c_str()); return FAILURE; } eRegistration = parseRegResponse(sResult); return SUCCESS; } ICellularRadio::CODE CellularRadio::getRegistration(REGISTRATION& eRegistration) { /* REGISTRATION_PRIORITY: * REGISTERED = 0 * ROAMING = 1 * DENIED = 2 * SEARCHING = 3 * NOT_REGISTERED = 4 * UNKNOWN = 5 */ uint8_t uRegPriority[] = { 4, 0, 3, 2, 5, 1 }; ICellularRadio::CODE ret = ERROR; REGISTRATION eReg = UNKNOWN; eRegistration = UNKNOWN; // We need to check CREG, CGREG, and CEREG for possible success. // Depending on the radio, carrier, roaming, sim card, cellular mode and some other factors the registration // will come back differently depending on the type of connection that is possible. const auto & commands = getRegistrationCommands(); for (const auto & cmd : commands) { ret = getRegistration(eReg, cmd); if (ret != SUCCESS) { break; } if (eReg == REGISTERED || eReg == ROAMING) { eRegistration = eReg; break; } eRegistration = (uRegPriority[eRegistration] > uRegPriority[eReg]) ? eReg : eRegistration; } return ret; } ICellularRadio::CODE CellularRadio::getCellularMode(CELLULAR_MODES &networks) { networks = CELLULAR_MODE_NA; std::string cmdResult = sendCommand("AT+COPS?"); if (cmdResult.find(ICellularRadio::RSP_OK) == std::string::npos) { printError("%s| AT+COPS returned unexpected response: AT+COPS? [%s]", getName().c_str(), cmdResult.c_str()); return FAILURE; } size_t cursor = 0; const std::vector &reply = MTS::Text::split(MTS::Text::getLine(MTS::Text::trim(cmdResult), cursor, cursor), ','); uint8_t op; if (reply.size() < 4 || !MTS::Text::parse(op, reply[3])) { printError("%s| AT+COPS Error parsing reply [AT+COPS?][%s]", getName().c_str(), cmdResult.c_str()); return FAILURE; } switch (op) { case 0: // GSM case 1: // GSM Compact case 3: // GSM w/EGPRS networks = CELLULAR_MODE_2G; break; case 2: // UTRAN case 4: // UTRAN w/HSDPA case 5: // UTRAN w/HSUPA case 6: // UTRAN w/HSDPA and HSUPA networks = CELLULAR_MODE_3G; break; case 7: // E-UTRAN, LTE case 8: // CAT M1, EC-GSM-IoT (A/Gb mode), LTE case 9: // NB IoT, E-UTRAN (NB-S1 mode), LTE networks = CELLULAR_MODE_4G; break; default: printError("%s| AT+COPS unknown Radio Access Technology [AT+COPS?][%s]", getName().c_str(), cmdResult.c_str()); return FAILURE; } return SUCCESS; } ICellularRadio::CODE CellularRadio::convertRegistrationToString(REGISTRATION eRegistration, std::string& sRegistration) { ICellularRadio::CODE eCode = FAILURE; switch (eRegistration) { case NOT_REGISTERED: sRegistration = ICellularRadio::VALUE_NOT_REGISTERED; eCode = SUCCESS; break; case REGISTERED: sRegistration = ICellularRadio::VALUE_REGISTERED; eCode = SUCCESS; break; case SEARCHING: sRegistration = ICellularRadio::VALUE_SEARCHING; eCode = SUCCESS; break; case DENIED: sRegistration = ICellularRadio::VALUE_DENIED; eCode = SUCCESS; break; case UNKNOWN: sRegistration = ICellularRadio::VALUE_UNKNOWN; eCode = SUCCESS; break; case ROAMING: sRegistration = ICellularRadio::VALUE_ROAMING; eCode = SUCCESS; break; } return eCode; } ICellularRadio::CODE CellularRadio::convertCellModesToString(ICellularRadio::CELLULAR_MODES eCellModes, std::string& sCellModes) { std::string sResult; if (eCellModes & CELLULAR_MODE_2G) { sResult += "2g,"; } if (eCellModes & CELLULAR_MODE_3G) { sResult += "3g,"; } if (eCellModes & CELLULAR_MODE_4G) { sResult += "4g,"; } if (eCellModes & CELLULAR_MODE_5G) { sResult += "5g,"; } if (!sResult.empty()) { sResult.pop_back(); // remove trailing comma } sCellModes = sResult; return SUCCESS; } ICellularRadio::CODE CellularRadio::unlockSimCard(const Json::Value& jArgs) { printTrace("%s| Unlock the SIM card using PIN code", m_sName.c_str()); if(!jArgs["pin"].isString()) { return INVALID_ARGS; } std::string sCmd = "AT+CPIN=" + jArgs["pin"].asString(); std::string sResult = sendCommand(sCmd, DEFAULT_BAIL_STRINGS, 5000); size_t pos = sResult.find(ICellularRadio::RSP_OK); if (pos == std::string::npos) { printWarning("%s| Failed to unlock the SIM card using command [%s]", m_sName.c_str(), sCmd.c_str()); return FAILURE; } return SUCCESS; } ICellularRadio::CODE CellularRadio::getSimCarrierCode(std::string& sCarrierCode) { CODE rc; printTrace("%s| Get carrier code from the SIM card installed", m_sName.c_str()); do { // Try to detect based on the ICCID std::string sIccid; rc = getIccid(sIccid); if (rc != SUCCESS) { printError("%s| Unable to determine SIM carrier: Failed to fetch SIM identifier", m_sName.c_str()); break; } printTrace("%s| Fetched ICCID: [%s]", m_sName.c_str(), sIccid.c_str()); rc = getSimCarrierCode(sIccid, sCarrierCode); if (rc != SUCCESS) { printError("%s| Unable to determine SIM carrier: Unable to extract carrier from the SIM identifier", m_sName.c_str()); break; } if (sCarrierCode != VALUE_UNKNOWN) { rc = SUCCESS; break; } // Fallback to the MCC/MNC detection std::string sMcc; std::string sMnc; rc = getSimMccMnc(sMcc, sMnc); if (rc != SUCCESS) { printError("%s| Unable to determine SIM carrier: Failed to fetch MCC/MNC from the SIM", m_sName.c_str()); break; } rc = getSimCarrierCode(sMcc, sMnc, sCarrierCode); if (rc != SUCCESS) { printError("%s| Unable to determine SIM carrier: Unable to extract carrier from MCC/MNC of the SIM", m_sName.c_str()); break; } } while(false); printTrace("%s| Detected carrier code: [%s]", m_sName.c_str(), sCarrierCode.c_str()); return rc; } ICellularRadio::CODE CellularRadio::getSimCarrierCode(const std::string& sIccid, std::string& sCarrierCode) { const char* ICCID_PREFIX_VZW = "891480"; const char* ICCID_PREFIX_ATT = "8901410"; if (sIccid.find(ICCID_PREFIX_VZW) == 0) { printTrace("%s| Verizon SIM detected", m_sName.c_str()); sCarrierCode = VALUE_CARRIER_CODE_VERIZON; } else if (sIccid.find(ICCID_PREFIX_ATT) == 0) { printTrace("%s| AT&T SIM detected", m_sName.c_str()); sCarrierCode = VALUE_CARRIER_CODE_ATT; } else { // All other carriers for which ICCID prefixes are not defined printWarning("%s| Carrier code is unknown for this SIM ID: [%s]", m_sName.c_str(), sIccid.c_str()); sCarrierCode = VALUE_UNKNOWN; } return SUCCESS; // no error cases for now } ICellularRadio::CODE CellularRadio::getSimCarrierCode(const std::string& sMcc, const std::string& sMnc, std::string& sCarrierCode) { const Json::Value& jLookup = MccMncTable::getInstance()->lookup(sMcc, sMnc); do { printTrace("%s| MCC-MNC Lookup: [%s][%s][%s]", m_sName.c_str(), sMcc.c_str(), sMnc.c_str(), jLookup.toStyledString().c_str()); if (jLookup.isNull()) { printWarning("%s| Carrier code is unknown for this MCC/NNC combination: [%s][%s]", m_sName.c_str(), sMcc.c_str(), sMnc.c_str()); sCarrierCode = VALUE_UNKNOWN; break; } if (jLookup["carrierCode"].asString().empty()) { printWarning("%s| Carrier code is unknown for this MCC/MNC combination: [%s][%s]", m_sName.c_str(), sMcc.c_str(), sMnc.c_str()); sCarrierCode = VALUE_UNKNOWN; break; } sCarrierCode = jLookup["carrierCode"].asString(); printTrace("%s| Detected carrier code by MCC/MNC: [%s]", m_sName.c_str(), sCarrierCode.c_str()); } while (false); return CODE::SUCCESS; } ICellularRadio::CODE CellularRadio::simAccessReadBinary(uint16_t iFileId, uint8_t iP1, uint8_t iP2, uint8_t iLe, std::string& sResult) { printTrace("%s| Read binary from the SIM Elementary File", m_sName.c_str()); // +CRSM=176,,,,[,[,]] std::string sCmd = "AT+CRSM=176,"; sCmd += MTS::Text::format(iFileId); sCmd += ','; sCmd += MTS::Text::format(iP1); sCmd += ','; sCmd += MTS::Text::format(iP2); sCmd += ','; sCmd += MTS::Text::format(iLe); std::string sRawResponse = sendCommand(sCmd, DEFAULT_BAIL_STRINGS, 3000); printTrace("%s| Raw response from the radio: [%s]", m_sName.c_str(), sRawResponse.c_str()); if (sRawResponse.empty()) { printError("%s| No response from the radio in 3 seconds.", m_sName.c_str()); return CODE::NO_RESPONSE; } if (sRawResponse.rfind(RSP_ERROR) != std::string::npos) { printError("%s| Failed to read from the SIM Elementary File: [%s]", m_sName.c_str(), sRawResponse.c_str()); return CODE::ERROR; } // Trim the output to remove excess whitespaces and line separators. sRawResponse = MTS::Text::trim(sRawResponse); // The response should start with "+CRSM: ". const std::string sResponsePrefix = "+CRSM: "; if (sRawResponse.rfind(sResponsePrefix, 0) != 0) { printError("%s| Unexpected response from the radio: [%s]", m_sName.c_str(), sRawResponse.c_str()); return CODE::FAILURE; } // Select eveything between the prefix and the next line. auto eolPos = sRawResponse.find(CR, sResponsePrefix.size()); sRawResponse = sRawResponse.substr(sResponsePrefix.size(), eolPos - sResponsePrefix.size()); // Split the output by commas. Example: 144,0,"00FFFF02" auto vOutput = MTS::Text::split(sRawResponse, ',', 3); if (vOutput.size() < 3) { printError("%s| Unexpected response from the radio: [%s]", m_sName.c_str(), sRawResponse.c_str()); return CODE::FAILURE; } // Two unquoted integers const std::string& sSw1 = vOutput[0]; const std::string& sSw2 = vOutput[1]; // Check if the SIM indicates any errors if (sSw1 != "144" || sSw2 != "0") { printError("%s| Unexpected response from the SIM: [%s]", m_sName.c_str(), sRawResponse.c_str()); return CODE::FAILURE; } // Quectel radios quote the third element of the output. Remove the quoting. const std::string& sResponse = MTS::Text::trim(vOutput[2], '"'); sResult = sResponse; return CODE::SUCCESS; } ICellularRadio::CODE CellularRadio::getSimMncLength(uint8_t& iLength) { printTrace("%s| Get SIM MNC length", m_sName.c_str()); const int iEfadId = 0x6FAD; const uint8_t iOffsetHigh = 0; const uint8_t iOffsetLow = 0; const uint8_t iNumBytes = 0; CODE rc; std::string sEFadContent; rc = simAccessReadBinary(iEfadId, iOffsetLow, iOffsetHigh, iNumBytes, sEFadContent); if (rc != CODE::SUCCESS) { printError("%s| Failed to determine the SIM MNC length", m_sName.c_str()); return rc; } // length of MNC in the IMSI is stored in byte 4 of EFad (indexing from 1) const uint8_t iMncLengthEfadIdx = 4; const uint8_t iCharsPerByte = 2; const uint8_t iMinEFadLength = iMncLengthEfadIdx * iCharsPerByte; if (sEFadContent.size() < iMinEFadLength) { printError("%s| SIM EFad does not contain an MNC length byte: [%s]", m_sName.c_str(), sEFadContent.c_str()); return CODE::FAILURE; } // read byte 4 of EFad (indexing from 1) with the MNC length const size_t iMncStartPosition = (iMncLengthEfadIdx - 1) * iCharsPerByte; const std::string sMncLength = sEFadContent.substr(iMncStartPosition, iCharsPerByte); uint8_t iMncLength; // parse hex to unsigned byte if (!MTS::Text::parseHex(iMncLength, sMncLength)) { printError("%s| Unexpected SIM EFad content: [%s]", m_sName.c_str(), sEFadContent.c_str()); return CODE::FAILURE; } // Only the lower 4 bits are used for MNC length, others are reserved for future use. iMncLength &= 0x0F; // Done iLength = iMncLength; printDebug("%s| Got MNC length of [%u]", m_sName.c_str(), iLength); return CODE::SUCCESS; } ICellularRadio::CODE CellularRadio::getSimMccMnc(std::string& sMccMnc) { printTrace("%s| Get MCC/MNC of the home network from the SIM", m_sName.c_str()); CODE rc; std::string sImsi; uint8_t iMncLength; do { rc = getImsi(sImsi); if (rc != CODE::SUCCESS) { printError("%s| Failed to get SIM IMSI", m_sName.c_str()); break; } if (sImsi.size() < 5) { printError("%s| Unexpected IMSI value: [%s]", m_sName.c_str(), sImsi.c_str()); rc = CODE::FAILURE; break; } rc = getSimMncLength(iMncLength); if (rc != CODE::SUCCESS) { printError("%s| Failed to determine the MNC length", m_sName.c_str()); break; } // MNC shall be 2 or 3 characters long if (iMncLength < 2 || iMncLength > 3) { printError("%s| Unexpected MNC length: [%u]", m_sName.c_str(), iMncLength); rc = CODE::FAILURE; break; } // PLMN code shall be 5 or 6 characters long const size_t mncLength = 3; const size_t plmnCodeLength = mncLength + iMncLength; sMccMnc = sImsi.substr(0, plmnCodeLength); // Done printDebug("%s| Got MCC/MNC of the home network from the SIM: [%s]", m_sName.c_str(), sMccMnc.c_str()); rc = CODE::SUCCESS; } while (false); return rc; } ICellularRadio::CODE CellularRadio::getSimMccMnc(std::string& sMcc, std::string& sMnc) { CODE rc; std::string sPlmnCode; rc = getSimMccMnc(sPlmnCode); if (rc == CODE::SUCCESS) { // PLMN code consists of MCC (first 3 digits) and MNC (second 2 or 3 digits) sMcc = sPlmnCode.substr(0, 3); sMnc = sPlmnCode.substr(3); } return rc; } ICellularRadio::CODE CellularRadio::validateMsl(const Json::Value&) { printTrace("%s| Validate MSL", m_sName.c_str()); return NOT_APPLICABLE; } ICellularRadio::CODE CellularRadio::setMsid(const Json::Value&) { printTrace("%s| Set MSID", m_sName.c_str()); return NOT_APPLICABLE; } ICellularRadio::CODE CellularRadio::getMipProfile(Json::Value&) { printTrace("%s| Get MIP Active Profile", m_sName.c_str()); return NOT_APPLICABLE; } ICellularRadio::CODE CellularRadio::setMipActiveProfile(const Json::Value&) { printTrace("%s| Set MIP Active Profile", m_sName.c_str()); return NOT_APPLICABLE; } ICellularRadio::CODE CellularRadio::setMipNai(const Json::Value&) { printTrace("%s| Set MIP NAI", m_sName.c_str()); return NOT_APPLICABLE; } ICellularRadio::CODE CellularRadio::setMipHomeIp(const Json::Value&) { printTrace("%s| Set MIP Home IP", m_sName.c_str()); return NOT_APPLICABLE; } ICellularRadio::CODE CellularRadio::setMipPrimaryHa(const Json::Value&) { printTrace("%s| Set MIP Primary HA", m_sName.c_str()); return NOT_APPLICABLE; } ICellularRadio::CODE CellularRadio::setMipSecondaryHa(const Json::Value&) { printTrace("%s| Set MIP Secondary HA", m_sName.c_str()); return NOT_APPLICABLE; } ICellularRadio::CODE CellularRadio::setMipMnAaaSpi(const Json::Value&) { printTrace("%s| Set MIP MN-AAA SPI", m_sName.c_str()); return NOT_APPLICABLE; } ICellularRadio::CODE CellularRadio::setMipMnHaSpi(const Json::Value&) { printTrace("%s| Set MIP MN-HA SPI", m_sName.c_str()); return NOT_APPLICABLE; } ICellularRadio::CODE CellularRadio::setMipRevTun(const Json::Value&) { printTrace("%s| Set MIP Rev Tun", m_sName.c_str()); return NOT_APPLICABLE; } ICellularRadio::CODE CellularRadio::setMipMnAaaSs(const Json::Value&) { printTrace("%s| Set MIP MN-AAA SS", m_sName.c_str()); return NOT_APPLICABLE; } ICellularRadio::CODE CellularRadio::setMipMnHaSs(const Json::Value&) { printTrace("%s| Set MIP MN-HA SS", m_sName.c_str()); return NOT_APPLICABLE; } ICellularRadio::CODE CellularRadio::updateDc(const Json::Value&, UpdateCb&) { printTrace("%s| Update Device Configuration", m_sName.c_str()); return NOT_APPLICABLE; } ICellularRadio::CODE CellularRadio::updatePrl(const Json::Value&, UpdateCb&) { printTrace("%s| Update Preferred Roaming List", m_sName.c_str()); return NOT_APPLICABLE; } ICellularRadio::CODE CellularRadio::updateFumo(const Json::Value&, UpdateCb&) { printTrace("%s| Update Firmware Update Management Object", m_sName.c_str()); 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()); return NOT_APPLICABLE; } ICellularRadio::CODE CellularRadio::activate(const Json::Value&, UpdateCb&) { printTrace("%s| Activation", m_sName.c_str()); return NOT_APPLICABLE; } ICellularRadio::CODE CellularRadio::startOmaDm(ICellularRadio::UpdateCb&) { printTrace("%s| Start OMA DM procedure: not applicable", m_sName.c_str()); return NOT_APPLICABLE; } ICellularRadio::CODE CellularRadio::setActiveFirmware(const Json::Value&) { printTrace("%s| Set Active Firmware Image Number: not applicable", m_sName.c_str()); return NOT_APPLICABLE; } ICellularRadio::CODE CellularRadio::getActiveFirmware(std::string& sFwId) { printTrace("%s| Get Active Firmware Image Number: not applicable", m_sName.c_str()); sFwId = ICellularRadio::VALUE_NOT_SUPPORTED; return NOT_APPLICABLE; } ICellularRadio::CODE CellularRadio::sendBasicCommand(const std::string& sCmd, int32_t iTimeoutMillis, const char& ESC) { std::string response = sendCommand(sCmd, DEFAULT_BAIL_STRINGS, iTimeoutMillis, ESC); if (response.size() == 0) { return NO_RESPONSE; } else if (response.find(ICellularRadio::RSP_OK) != std::string::npos) { return SUCCESS; } else if (response.find(ICellularRadio::RSP_ERROR) != std::string::npos) { return ERROR; } else { return FAILURE; } } std::string CellularRadio::sendCommand(const std::string& sCmd, const std::vector& vBail, int32_t timeoutMillis, const char& ESC) { return ICellularRadio::sendCommand(m_apIo, sCmd, vBail, timeoutMillis, ESC); } std::string CellularRadio::sendCommand(const std::string& sCmd, MTS::IO::CellularRadio::IsNeedMoreData& isNeedMoreData, int32_t timeoutMillis, const char& ESC) { return ICellularRadio::sendCommand(m_apIo, sCmd, isNeedMoreData, timeoutMillis, ESC); } std::string CellularRadio::waitResponse(const std::vector& vBail, int32_t timeoutMillis) { return ICellularRadio::waitResponse(m_apIo, vBail, timeoutMillis); } std::string CellularRadio::waitResponse(ICellularRadio::IsNeedMoreData& isNeedMoreData, int32_t timeoutMillis) { return ICellularRadio::waitResponse(m_apIo, isNeedMoreData, timeoutMillis); } 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(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 vParts = MTS::Text::split(sLine, ":", 2); if(vParts.size() == 2 && vParts[0] == sKey) { if(eType == Json::ValueType::stringValue) { jParent[sJsonKey] = MTS::Text::trim(vParts[1]); } else if (eType == Json::ValueType::intValue || eType == Json::ValueType::uintValue) { //TODO: printWarning("%s| Unable to parse requested type from line [%s]", getName().c_str(), sKey.c_str(), sLine.c_str()); return false; } else if(eType == Json::ValueType::realValue) { //TODO: printWarning("%s| Unable to parse requested type from line [%s]", getName().c_str(), sKey.c_str(), sLine.c_str()); return false; } else if(eType == Json::ValueType::booleanValue) { //TODO: printWarning("%s| Unable to parse requested type from line [%s]", getName().c_str(), sKey.c_str(), sLine.c_str()); return false; } else { printWarning("%s| Unable to parse requested type from line [%s]", getName().c_str(), sKey.c_str(), sLine.c_str()); return false; } } else { printWarning("%s| Unable to parse %s from line [%s]", getName().c_str(), sKey.c_str(), sLine.c_str()); return false; } return true; } bool CellularRadio::getCarrierFromFirmware(const std::string& sFirmware, std::string& sCarrier) { // Assuming that this function is not supported by the modem until overriden. return false; } bool CellularRadio::getHardwareVersionFromFirmware(const std::string& sFirmware, std::string& sHardware) { // Assuming that this function is not supported by the modem until overriden. return false; } const char *CellularRadio::RadioBandMap::getLTEBand(const int32_t channel) { for (unsigned int ii = 0; ii < NUM_LTE_BANDS; ii++) { if (EULTRAband[ii].low <= channel && EULTRAband[ii].high >= channel) { return EULTRAband[ii].name; } } return ICellularRadio::VALUE_UNKNOWN; } const char *CellularRadio::RadioBandMap::getCDMABand(const int channel) { for (unsigned int ii = 0; ii < NUM_WCDMA_BANDS; ii++) { if (WCDMAband[ii].low <= channel && WCDMAband[ii].high >= channel) { return WCDMAband[ii].name; } } return ICellularRadio::VALUE_UNKNOWN; } const char *CellularRadio::RadioBandMap::getGSMBand(const int channel) { for (unsigned int ii = 0; ii < NUM_GSM_BANDS; ii++) { if (GSMband[ii].low <= channel && GSMband[ii].high >= channel) { return GSMband[ii].name; } } return ICellularRadio::VALUE_UNKNOWN; } const char *CellularRadio::RadioBandMap::getRadioBandName() { const char *band = ICellularRadio::VALUE_UNKNOWN; if (m_sRadioType == ICellularRadio::VALUE_TYPE_LTE) { band = getLTEBand(m_iChannel); } else if (m_sRadioType == ICellularRadio::VALUE_TYPE_CDMA) { band = getCDMABand(m_iChannel); } else if (m_sRadioType == ICellularRadio::VALUE_TYPE_GSM) { band = getGSMBand(m_iChannel); } return band; } const char *CellularRadio::RadioBandMap::getRadioBandName(const std::string &channel, const std::string &radioType) { const char *band = ICellularRadio::VALUE_UNKNOWN; int32_t chan = strtol(channel.c_str(), NULL, 10); if (radioType == ICellularRadio::VALUE_TYPE_LTE) { band = getLTEBand(chan); } else if (radioType == ICellularRadio::VALUE_TYPE_CDMA) { band = getCDMABand(chan); } else if (radioType == ICellularRadio::VALUE_TYPE_GSM) { band = getGSMBand(chan); } 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(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(dReadCount); if (duReadCount == 0) { // EOF. Return what was already read nReadBytes = nUsedBuffer; rc = SUCCESS; break; } nUsedBuffer += duReadCount; } return rc; } ICellularRadio::CODE CellularRadio::setUeModeOfOperation(ICellularRadio::UE_MODES_OF_OPERATION mode) { printTrace("%s| Set UE Mode Of Operation: not applicable", m_sName.c_str()); return NOT_APPLICABLE; } ICellularRadio::CODE CellularRadio::getUeModeOfOperation(ICellularRadio::UE_MODES_OF_OPERATION& mode) { printTrace("%s| Get UE Mode Of Operation: not applicable", m_sName.c_str()); mode = ICellularRadio::UE_MODES_OF_OPERATION::UNKNOWN_MODE; return NOT_APPLICABLE; } ICellularRadio::CODE CellularRadio::disableVoiceSupport() { printTrace("%s| Disable Voice Support: not applicable", m_sName.c_str()); return NOT_APPLICABLE; } ICellularRadio::CODE CellularRadio::getVoiceSupport(Json::Value& jData) { bool bVoiceEnabled, bSmsOnly; CODE rc; rc = getVoiceSupport(bVoiceEnabled, bSmsOnly); if (rc == SUCCESS) { jData[KEY_VOICE_ENABLED] = bVoiceEnabled; jData[KEY_SMS_ONLY] = bSmsOnly; } return rc; } ICellularRadio::CODE CellularRadio::getVoiceSupport(bool&, bool&) { printTrace("%s| Get Voice Support: not applicable", m_sName.c_str()); return NOT_APPLICABLE; } ICellularRadio::CODE CellularRadio::sendBasicQuery(const std::string& sCmd, const std::string& sLabel, std::string& sResult, int32_t iTimeoutMillis, const char& ESC) { sResult = sendCommand(sCmd, DEFAULT_BAIL_STRINGS, iTimeoutMillis, ESC); printTrace("%s| Got response from the radio: [%s]", getName().c_str(), sResult.c_str()); if (sResult.empty()) { return NO_RESPONSE; } if (sResult.rfind(ICellularRadio::RSP_ERROR) != std::string::npos) { return ERROR; } size_t end = sResult.rfind(ICellularRadio::RSP_OK); if (end == std::string::npos) { printError("%s| \"OK\" not found in the response: [%s]", getName().c_str(), sResult.c_str()); return FAILURE; } size_t start = sResult.find(sLabel); if (start == std::string::npos) { printError("%s| Unexpected radio response: [%s]", getName().c_str(), sResult.c_str()); return FAILURE; } start += sLabel.length(); sResult = MTS::Text::trim(sResult.substr(start, end-start)); return SUCCESS; } ICellularRadio::CODE CellularRadio::isCommandSupported(const std::string& sCmd, bool& bIsSupported) { CODE rc; bIsSupported = false; rc = sendBasicCommand(sCmd); if (SUCCESS == rc) { bIsSupported = true; } else if (ERROR == rc) { // if not applicable rc = SUCCESS; } return rc; } ICellularRadio::CODE CellularRadio::getSelectedBandsRaw(std::string& sRawBands) { printTrace("%s| Acquiring selected bands: not applicable", m_sName.c_str()); return NOT_APPLICABLE; } ICellularRadio::CODE CellularRadio::convertPdpContextAuthTypeToString(PDP_CONTEXT_AUTH_TYPE eAuthType, std::string& sAuthType) { CODE rc = FAILURE; switch (eAuthType) { case NONE: sAuthType = VALUE_PDP_CONTEXT_AUTH_TYPE_NONE; rc = SUCCESS; break; case PAP: sAuthType = VALUE_PDP_CONTEXT_AUTH_TYPE_PAP; rc = SUCCESS; break; case CHAP: sAuthType = VALUE_PDP_CONTEXT_AUTH_TYPE_CHAP; rc = SUCCESS; break; case PAP_CHAP: sAuthType = VALUE_PDP_CONTEXT_AUTH_TYPE_PAP_CHAP; rc = SUCCESS; break; default: sAuthType = ICellularRadio::VALUE_UNKNOWN; break; } return rc; } ICellularRadio::CODE MTS::IO::CellularRadio::convertStringToPdpContextAuthType(const std::string& sAuthType, PDP_CONTEXT_AUTH_TYPE& eAuthType) { CODE rc = FAILURE; if (VALUE_PDP_CONTEXT_AUTH_TYPE_NONE == sAuthType) { eAuthType = PDP_CONTEXT_AUTH_TYPE::NONE; rc = SUCCESS; } else if (VALUE_PDP_CONTEXT_AUTH_TYPE_PAP == sAuthType) { eAuthType = PDP_CONTEXT_AUTH_TYPE::PAP; rc = SUCCESS; } else if (VALUE_PDP_CONTEXT_AUTH_TYPE_CHAP == sAuthType) { eAuthType = PDP_CONTEXT_AUTH_TYPE::CHAP; rc = SUCCESS; } else if (VALUE_PDP_CONTEXT_AUTH_TYPE_PAP_CHAP == sAuthType) { eAuthType = PDP_CONTEXT_AUTH_TYPE::PAP_CHAP; rc = SUCCESS; } return rc; } // When there are no defined contexts // Telit returns: // OK // Quectel returns: // +CGDCONT: // // OK ICellularRadio::CODE CellularRadio::getPdpContextsBase(Json::Value& jData) { CODE rc; const std::string sCommand = "AT+CGDCONT?"; const int dTimeout = 1000; std::string sResult; jData = Json::objectValue; rc = sendBasicQuery(sCommand, "", sResult, dTimeout); if (rc != SUCCESS) { return rc; } std::vector vContexts = MTS::Text::split(sResult, "+CGDCONT:"); for (size_t i = 0; i < vContexts.size(); i++) { vContexts[i] = MTS::Text::trim(vContexts[i]); if (vContexts[i].empty()) { continue; } std::vector vContextParams = MTS::Text::split(vContexts[i], ",", 4); if (vContextParams.size() < 3) { return FAILURE; } std::string sContextId = vContextParams[0]; std::string sIpMode = MTS::Text::trim(vContextParams[1], '"'); // Remove double quotes from the start and end of the value std::string sApn = MTS::Text::trim(vContextParams[2], '"'); // Remove double quotes from the start and end of the value jData[sContextId][ICellularRadio::KEY_PDP_CONTEXT_IPMODE] = sIpMode; jData[sContextId][ICellularRadio::KEY_PDP_CONTEXT_APN] = sApn; } return SUCCESS; } ICellularRadio::CODE CellularRadio::getPdpContexts(Json::Value& jData) { printTrace("%s| Fetching the list of PDP contexts from the radio", getName().c_str()); CODE rc; bool bPdpAuthSupported; // check if pdp context authentication is applicable rc = isPdpContextAuthSupported(bPdpAuthSupported); if (SUCCESS != rc) { return rc; } rc = getPdpContextsBase(jData); if (SUCCESS != rc) { printError("%s| Failed to fetch the list of the defined PDP contexts from the radio", getName().c_str()); return rc; } if (bPdpAuthSupported) { rc = fillPdpContextAuthFields(jData); if (SUCCESS != rc) { printError("%s| Failed to fetch the authentication parameters for PDP contexts", getName().c_str()); return rc; } } return SUCCESS; } ICellularRadio::CODE CellularRadio::mergePdpContexts(const std::string& sId, const Json::Value& jCurrentContext, const Json::Value& jChanges, Json::Value& jResult) { jResult[KEY_PDP_CONTEXT_ID] = sId; if (jChanges.isMember(KEY_PDP_CONTEXT_IPMODE)) { jResult[KEY_PDP_CONTEXT_IPMODE] = jChanges[KEY_PDP_CONTEXT_IPMODE].asString(); } else if (jCurrentContext.isMember(KEY_PDP_CONTEXT_IPMODE)) { printInfo("%s| Re-using IP Mode [%s] for PDP context [%s]", getName().c_str(), jCurrentContext[KEY_PDP_CONTEXT_IPMODE].asString().c_str(), sId.c_str()); jResult[KEY_PDP_CONTEXT_IPMODE] = jCurrentContext[KEY_PDP_CONTEXT_IPMODE].asString(); } if (jChanges.isMember(KEY_PDP_CONTEXT_APN)) { jResult[KEY_PDP_CONTEXT_APN] = jChanges[KEY_PDP_CONTEXT_APN].asString(); } else if (jCurrentContext.isMember(KEY_PDP_CONTEXT_APN)) { printInfo("%s| Re-using APN [%s] for PDP context [%s]", getName().c_str(), jCurrentContext[KEY_PDP_CONTEXT_APN].asString().c_str(), sId.c_str()); jResult[KEY_PDP_CONTEXT_APN] = jCurrentContext[KEY_PDP_CONTEXT_APN].asString(); } if (jChanges.isMember(KEY_PDP_CONTEXT_AUTH_TYPE)) { jResult[KEY_PDP_CONTEXT_AUTH_TYPE] = jChanges[KEY_PDP_CONTEXT_AUTH_TYPE].asString(); } if (jChanges.isMember(KEY_PDP_CONTEXT_AUTH_USERNAME)) { jResult[KEY_PDP_CONTEXT_AUTH_USERNAME] = jChanges[KEY_PDP_CONTEXT_AUTH_USERNAME].asString(); } if (jChanges.isMember(KEY_PDP_CONTEXT_AUTH_PASSWORD)) { jResult[KEY_PDP_CONTEXT_AUTH_PASSWORD] = jChanges[KEY_PDP_CONTEXT_AUTH_PASSWORD].asString(); } return SUCCESS; } ICellularRadio::CODE CellularRadio::initPdpContextInfo(const Json::Value& jConfig, PdpContextInfo& pdpContextResult) { // validate IP mode if (jConfig[KEY_PDP_CONTEXT_IPMODE].asString() != VALUE_PDP_CONTEXT_IP_MODE_IP && jConfig[KEY_PDP_CONTEXT_IPMODE].asString() != VALUE_PDP_CONTEXT_IP_MODE_IPV6 && jConfig[KEY_PDP_CONTEXT_IPMODE].asString() != VALUE_PDP_CONTEXT_IP_MODE_IPV4V6) { printError("%s| The PDP context IP mode value is invalid [%s]", getName().c_str(), jConfig[KEY_PDP_CONTEXT_IPMODE].asString().c_str()); return INVALID_ARGS; } // validate authentication fields only if it is present if (jConfig.isMember(KEY_PDP_CONTEXT_AUTH_TYPE)) { // check if the PDP context authentication is supported by the radio const auto& sRequested = jConfig[KEY_PDP_CONTEXT_AUTH_TYPE].asString(); const auto& vSupported = getSupportedPdpContextAuthTypes(); auto supportCheck = [sRequested](const std::string& sElement) { return sRequested == sElement; }; if (std::none_of(vSupported.begin(), vSupported.end(), supportCheck)) { printError("%s| PDP context parameters: authentication type [%s] is not supported", getName().c_str(), jConfig[KEY_PDP_CONTEXT_AUTH_TYPE].asString().c_str()); return INVALID_ARGS; } } if (! jConfig.isMember(KEY_PDP_CONTEXT_AUTH_TYPE) && (jConfig.isMember(KEY_PDP_CONTEXT_AUTH_USERNAME) || jConfig.isMember(KEY_PDP_CONTEXT_AUTH_PASSWORD))) { printError("%s| PDP context authentication type is not provided, but username or password are given", getName().c_str()); return INVALID_ARGS; } if (jConfig.isMember(KEY_PDP_CONTEXT_AUTH_TYPE) && jConfig[KEY_PDP_CONTEXT_AUTH_TYPE].asString() != VALUE_PDP_CONTEXT_AUTH_TYPE_NONE && ! (jConfig.isMember(KEY_PDP_CONTEXT_AUTH_USERNAME) && jConfig.isMember(KEY_PDP_CONTEXT_AUTH_PASSWORD))) { printError("%s| PDP context authentication type [%s]. The username or password are not provided", getName().c_str(), jConfig[KEY_PDP_CONTEXT_AUTH_TYPE].asString().c_str()); return INVALID_ARGS; } // fills with empty string if there are no such fields pdpContextResult.sId = jConfig[KEY_PDP_CONTEXT_ID].asString(); pdpContextResult.sIpMode = jConfig[KEY_PDP_CONTEXT_IPMODE].asString(); pdpContextResult.sApn = jConfig[KEY_PDP_CONTEXT_APN].asString(); pdpContextResult.sAuthType = jConfig[KEY_PDP_CONTEXT_AUTH_TYPE].asString(); pdpContextResult.sUsername = jConfig[KEY_PDP_CONTEXT_AUTH_USERNAME].asString(); pdpContextResult.sPassword = jConfig[KEY_PDP_CONTEXT_AUTH_PASSWORD].asString(); return SUCCESS; } ICellularRadio::CODE CellularRadio::isPdpContextAuthEditRequired(const Json::Value& jConfig, bool& bIsAuthEditRequired) { bIsAuthEditRequired = false; if (jConfig.isMember(KEY_PDP_CONTEXT_AUTH_TYPE) || jConfig.isMember(KEY_PDP_CONTEXT_AUTH_PASSWORD) || jConfig.isMember(KEY_PDP_CONTEXT_AUTH_USERNAME)) { bIsAuthEditRequired = true; } return SUCCESS; } ICellularRadio::CODE CellularRadio::setPdpContextBase(const PdpContextInfo& pdpContext) { std::string sCommand = "AT+CGDCONT="; const int dTimeout = 1000; sCommand += pdpContext.sId; sCommand += ",\""; sCommand += pdpContext.sIpMode; sCommand += "\",\""; sCommand += pdpContext.sApn; sCommand += "\""; return sendBasicCommand(sCommand, dTimeout); } ICellularRadio::CODE CellularRadio::deletePdpContext(const std::string& sId) { std::string sCommand = "AT+CGDCONT=" + sId; const int dTimeout = 1000; return sendBasicCommand(sCommand, dTimeout); } ICellularRadio::CODE CellularRadio::setPdpContext(const std::string& sId, const Json::Value& jConfig) { printTrace("%s| Setting PDP context [%s] to the radio", getName().c_str(), sId.c_str()); CODE rc; Json::Value jCurrentContexts; bool bPdpAuthSupported; bool bPdpAuthEditRequired; PdpContextInfo pdpContext; if (sId.empty()) { printError("%s| PDP Context ID is not specified", getName().c_str()); return INVALID_ARGS; } rc = getPdpContexts(jCurrentContexts); if (SUCCESS != rc) { return rc; } // DELETE context if (jConfig.empty()) { rc = deletePdpContext(sId); if (SUCCESS != rc) { printError("%s| Failed to delete PDP context [%s]", getName().c_str(), sId.c_str()); } return rc; } // ADD or EDIT context // check if PDP context authentication is applicable (void) isPdpContextAuthEditRequired(jConfig, bPdpAuthEditRequired); rc = isPdpContextAuthSupported(bPdpAuthSupported); if (SUCCESS != rc) { return rc; } if (! bPdpAuthSupported && bPdpAuthEditRequired) { printError("%s| PDP context authentication is not supported by this radio, but arguments are provided", getName().c_str()); return INVALID_ARGS; } Json::Value jResult; // Edit - fetch missing arguments from the current state. (void) mergePdpContexts(sId, jCurrentContexts[sId], jConfig, jResult); // validate fields and create a PDP context structure to use in the commands rc = initPdpContextInfo(jResult, pdpContext); if (SUCCESS != rc) { return rc; } // setting context rc = setPdpContextBase(pdpContext); if (SUCCESS != rc) { return rc; } if (bPdpAuthSupported && bPdpAuthEditRequired) { return setPdpContextAuth(pdpContext); } return SUCCESS; } ICellularRadio::CODE CellularRadio::getDiagnostics(std::string& sReport) { // Clear the original content before using the string as a buffer. sReport.clear(); // Determine whether the SIM card is locked to select the appropriate // list of commands for this radio. std::string sSimLockStatus; CODE iSimLockRet = getSimLockStatus(sSimLockStatus); // Re-use the SIM status detection logic from TelitRadio::getNetworkStatus. // The SIM should be inserted and NOT locked. bool bIsSimReady = (iSimLockRet == CODE::SUCCESS && sSimLockStatus != "SIM PIN" && sSimLockStatus != "SIM PUK"); // Determine the list of diagnostic commands required for this radio. const auto& vCommands = getDiagCommands(bIsSimReady); // For all commands - set the maximum timeout to 2 seconds. const int iMaxTimeout = 2000; // Allow the radio to ignore up to 2 consecutive queries before giving up. int iMaxNoResponseCount = 2; int iNoResponseCount = 0; // Execute each of the commands, add their output to the report. for (const auto& sCmd : vCommands) { // First line - the command to execute. sReport.append(sCmd); sReport.push_back(ICellularRadio::CR); sReport.push_back(ICellularRadio::NL); // Execute the command. std::string sResult = sendCommand(sCmd, ICellularRadio::DEFAULT_BAIL_STRINGS, iMaxTimeout); // Count the number of commands ignored by the radio. // Normally, the radio should not ignore any of the commands, // but radios have their own bugs. if (sResult.empty()) { printWarning("%s| Failed to execute the [%s] command - no response from the radio in [%d] ms", getName().c_str(), sCmd.c_str(), iMaxTimeout); sResult = "\r\n"; ++iNoResponseCount; } else { iNoResponseCount = 0; } // If the radio ignored too many commands - probably it is stuck and will not // return to operation on its own. There is no point in waiting any longer. if (iNoResponseCount >= iMaxNoResponseCount) { printError("%s| Failed to execute the diagnostic commands - the radio has stopped responding", getName().c_str()); return CODE::NO_RESPONSE; } // Append the command output to the report. sReport.append(sResult); } // All commands returned a non-empty output. return CODE::SUCCESS; }