/* * 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 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} }; } 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 setEcho(m_bEchoEnabled); 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+GMI"); 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); 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()); if(m_sCarrier == "") { Json::Value jData; if(getNetworkStatus(jData) == SUCCESS) { if(jData.isMember(ICellularRadio::KEY_MCC) && jData.isMember(ICellularRadio::KEY_MNC)) { std::string sMcc = jData[ICellularRadio::KEY_MCC].asString(); std::string sMnc = jData[ICellularRadio::KEY_MNC].asString(); 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)) { m_sCarrier = jLookup[ICellularRadio::KEY_CARRIER].asString(); } else { printWarning("%s| MCC-MNC Lookup did not contain carrier", m_sName.c_str()); return FAILURE; } } else { printWarning("%s| Network Status did no contain MCC or MNC", m_sName.c_str()); return FAILURE; } } else { return FAILURE; } } sCarrier = m_sCarrier; 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 = ""; 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 from response [%s]", m_sName.c_str(), sResult.c_str()); return FAILURE; } //The Date format is YY/MM/DD -> Change to MM/DD/YY sDate = vDateParts[1] + "/" + vDateParts[2] + "/" + vDateParts[0]; vParts = MTS::Text::split(vParts[1], '-'); if(vParts.size() != 2) { printWarning("%s| Unable to parse Time from response [%s]", m_sName.c_str(), sResult.c_str()); return FAILURE; } sTime = vParts[0]; int32_t iZoneUnits; //the difference, expressed in quarters of an hour, between the local time and GMT if(!MTS::Text::parse(iZoneUnits, MTS::Text::strip(vParts[1], '+'))) { printWarning("%s| Unable to parse Time Zone from response [%s]", m_sName.c_str(), sResult.c_str()); return FAILURE; } int32_t iZone = iZoneUnits/4; //Divide by 4 to get hours difference int32_t iZonePartial = (iZoneUnits % 4) * 15; //Remainder in minutes std::string sPlusSign = "+"; if(iZonePartial < 0) { //Remove negative sign from partial and clear plus sign component iZonePartial *= -1; sPlusSign = ""; } std::stringstream ss; ss << sPlusSign << iZone; if(iZonePartial != 0) { ss << ":" << iZonePartial; } sTimeZone = ss.str(); return SUCCESS; } else { printWarning("%s| Unable to get 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; } const std::vector CellularRadio::getRegistrationCommands() { std::string sType; convertModelToType(getName(), sType); if (sType == VALUE_TYPE_LTE) { return { "CREG", "CGREG", "CEREG" }; } else { return { "CREG", "CGREG" }; } } 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) { std::string sIccid; CODE rc; printTrace("%s| Get carrier code from the SIM card installed", m_sName.c_str()); rc = getIccid(sIccid); if (rc != SUCCESS) { printError("%s| Unable to determine SIM carrier: Failed to fetch SIM identifier", m_sName.c_str()); return rc; } 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()); return rc; } 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 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::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; }