/* * Copyright (C) 2019 by Multi-Tech Systems * * This file is part of libmts-io. * * libmts-io is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * libmts-io is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with libmts-io. If not, see . * */ #include #include #include #include using namespace MTS::IO; TelitRadio::TelitRadio(const std::string& sName, const std::string& sRadioPort) : CellularRadio(sName, sRadioPort) { } bool TelitRadio::resetRadio(uint32_t iTimeoutMillis) { printInfo("%s| Rebooting radio", getName().c_str()); if(sendBasicCommand("AT#REBOOT") == SUCCESS) { if(iTimeoutMillis > 5000) { MTS::Thread::sleep(5000); iTimeoutMillis -= 5000; } return resetConnection(iTimeoutMillis); } return false; } CellularRadio::CODE TelitRadio::getModel(std::string& sModel) { printTrace("%s| Get Model", getName().c_str()); //Always returns SUCCESS because the model should be m_sName sModel = getName(); std::string sCmd("ATI4"); std::string sResult = sendCommand(sCmd); if (sResult.find("OK") == std::string::npos) { printWarning("%s| Unable to get model from radio. Returning [%s]", getName().c_str(), getName().c_str()); return SUCCESS; } else { sModel = CellularRadio::extractModelFromResult(sResult); if(sModel.size() == 0) { printWarning("%s| Unable to get model from radio. Returning [%s]", getName().c_str(), getName().c_str()); return SUCCESS; } } printDebug("%s| Extracted [%s] from [%s] query", getName().c_str(), sModel.c_str(), sCmd.c_str()); if(sModel != getName()) { printWarning("%s| Model identified [%s] does not match expected [%s]. Returning [%s]", getName().c_str(), sModel.c_str(), getName().c_str(), sModel.c_str()); } return SUCCESS; } CellularRadio::CODE TelitRadio::getIccid(std::string& sIccid) { printTrace("%s| Get ICCID", getName().c_str()); sIccid = VALUE_NOT_SUPPORTED; std::string sCmd("AT#CCID"); std::string sResult = CellularRadio::sendCommand(sCmd); size_t end = sResult.find(RSP_OK); if (end == std::string::npos) { printWarning("%s| Unable to get ICCID from radio using command [%s]", getName().c_str(), sCmd.c_str()); return FAILURE; } size_t start = sResult.find("#CCID:"); if(start != std::string::npos) { start += sizeof("#CCID:"); sIccid = MTS::Text::trim(sResult.substr(start, end-start)); if(sIccid.size() == 0) { printWarning("%s| Unable to get ICCID from radio using command [%s]", getName().c_str(), sCmd.c_str()); return FAILURE; } } return SUCCESS; } CellularRadio::CODE TelitRadio::getService(std::string& sService) { printTrace("%s| Get Service", getName().c_str()); sService = VALUE_NOT_SUPPORTED; std::string sCmd("AT#PSNT?"); std::string sResult = CellularRadio::sendCommand(sCmd); size_t end = sResult.find(RSP_OK); if (end == std::string::npos) { printWarning("%s| Unable to get Service from radio using command [%s]", getName().c_str(), sCmd.c_str()); return FAILURE; } size_t start = sResult.find(","); if(start != std::string::npos) { start += 1; //comma std::string sPsnt = MTS::Text::trim(sResult.substr(start, end-start)); int32_t iService; sscanf(sPsnt.c_str(), "%d", &iService); switch(iService) { case 0: sService = "GPRS"; break; case 1: sService = "EGPRS"; break; case 2: sService = "WCDMA"; break; case 3: sService = "HSDPA"; break; case 4: sService = "LTE"; break; default: sService = VALUE_UNKNOWN; break; } printDebug("%s| Service ID: [%d][%s]", getName().c_str(), iService, sService.c_str()); } return SUCCESS; } CellularRadio::CODE TelitRadio::getNetwork(std::string& sNetwork) { Json::Value jData; printTrace("%s| Get Network", getName().c_str()); sNetwork = VALUE_NOT_SUPPORTED; if(getNetworkStatus(jData) == SUCCESS) { if(jData.isMember(KEY_NETWORK)) { sNetwork = jData[KEY_NETWORK].asString(); return SUCCESS; } } return FAILURE; } /* AT#RFSTS - NETWORK STATUS (GSM network) #RFSTS:,,,,,,,,,,,,, Where: - Country code and operator code(MCC, MNC) - GSM Assigned Radio Channel - Received Signal Strength Indication - Localization Area Code - Routing Area Code - Tx Power - Mobility Management state - Radio Resource state - Network Operator Mode - Cell ID - International Mobile Subscriber Identity - Operator name - Service Domain 0 - No Service 1 - CS only 2 - PS only 3 - CS+PS - Active Band 1 - GSM 850 2 - GSM 900 3 - DCS 1800 4 - PCS 1900 (WCDMA network) #RFSTS: ,,,,, RSSI>,,,,,,,,,,, ,,[,,] Where: - Country code and operator code(MCC, MNC) - UMTS Assigned Radio Channel - Active PSC(Primary Synchronization Code) - Active Ec/Io(chip energy per total wideband power in dBm) - Active RSCP (Received Signal Code Power in dBm) - Received Signal Strength Indication - Localization Area Code - Routing Area Code - Tx Power - Discontinuous reception cycle Length (cycle length in ms) - Mobility Management state - Radio Resource state - Network Operator Mode - Block Error Rate (e.g., 005 means 0.5 %) - Cell ID - International Mobile Station ID - Operator name - Service Domain (see above) - Number of Active Set (Maximum 6) UARFCN of n th active set PSC of n th active set Ec/Io of n th active Set (LTE Network) #RFSTS: - - - - - - [] - - - - - - [] - - - */ CellularRadio::CODE TelitRadio::getNetworkStatus(Json::Value& jData) { int32_t iValue; std::string sValue; const uint32_t GSM_NETWORK_FORMAT = 14; const uint32_t WCDMA_NETWORK_FORMAT = 19; const uint32_t LTE_NETWORK_FORMAT = 16; printTrace("%s| Get Network Status", getName().c_str()); //Always get common network stats because this should never fail //This way the basic stats are always returned even if AT#RFSTS fails below getCommonNetworkStats(jData); std::string sCmd; std::string sResult; // LE910 radios have a bug where issuing AT#RFSTS with a locked SIM // will cause the radio to stop responding until a radio power cycle // Telit Support Portal Case #5069697 // LE910C1-NS is an LE910, so we stop the scan after the 0. if (getName().find("LE910") != std::string::npos) { sCmd = "AT+CPIN?"; sResult = sendCommand(sCmd); if (sResult.find("+CPIN:") == std::string::npos) { printDebug("%s| AT+CPIN? returned unexpected response: [%s][%s]", getName().c_str(), sCmd.c_str(), sResult.c_str()); printTrace("%s| Network Status:\n%s\n", getName().c_str(), jData.toStyledString().c_str()); return SUCCESS; //return SUCCESS because getCommonNetworkStats() succeeded at top of this function } if (sResult.find("SIM PIN") != std::string::npos) { printError("%s| The SIM is locked and must first be unlocked", getName().c_str()); printTrace("%s| Network Status:\n%s\n", getName().c_str(), jData.toStyledString().c_str()); return SUCCESS; //return SUCCESS because getCommonNetworkStats() succeeded at top of this function } } sCmd = "AT#RFSTS"; sResult = sendCommand(sCmd, DEFAULT_BAIL_STRINGS, 200); if (sResult.find("#RFSTS:") == std::string::npos) { //On LTE radios without signal, this case will run because AT#RFSTS just returns "OK" printDebug("%s| Network Status command returned unexpected response: [%s][%s]", getName().c_str(), sCmd.c_str(), sResult.c_str()); printTrace("%s| Network Status:\n%s\n", getName().c_str(), jData.toStyledString().c_str()); return SUCCESS; //return SUCCESS because getCommonNetworkStats() succeeded at top of this function } size_t start = sResult.find(":") + 1; //Position right after "#RFSTS:" std::vector vParts = MTS::Text::split(MTS::Text::trim(sResult.substr(start)), ","); if (vParts.size() < 3) { printDebug("%s| Network Status command reponse is an unknown format: [%s][%s]", getName().c_str(), sCmd.c_str(), sResult.c_str()); printTrace("%s| Network Status:\n%s\n", getName().c_str(), jData.toStyledString().c_str()); return SUCCESS; //return SUCCESS because getCommonNetworkStats() succeeded at top of this function } else { //Country Code and Operator Code std::vector vPLMN = MTS::Text::split(vParts[0], ' '); if(vPLMN.size() == 2) { jData[KEY_MCC] = MTS::Text::strip(vPLMN[0], '"'); jData[KEY_MNC] = MTS::Text::strip(vPLMN[1], '"'); } jData[KEY_CHANNEL] = vParts[1]; } if (vParts.size() == GSM_NETWORK_FORMAT ) { //Parse as GSM Network Format jData[KEY_RSSIDBM] = vParts[2]; jData[KEY_LAC] = vParts[3]; jData[KEY_RAC] = vParts[4]; jData[KEY_TXPWR] = vParts[5]; jData[KEY_MM] = vParts[6]; jData[KEY_RR] = vParts[7]; jData[KEY_NOM] = vParts[8]; jData[KEY_CID] = vParts[9]; jData[KEY_IMSI] = MTS::Text::strip(vParts[10], '"'); jData[KEY_NETWORK] = MTS::Text::strip(vParts[11], '"'); if(MTS::Text::parse(iValue, vParts[12]) && convertServiceDomainToString((SERVICEDOMAIN)iValue, sValue) == SUCCESS) { jData[KEY_SD] = sValue; } if(MTS::Text::parse(iValue, vParts[13]) && convertActiveBandToString((ACTIVEBAND)iValue, sValue) == SUCCESS) { jData[KEY_ABND] = sValue; } // IN003567 ME910C1 radios have some odd behavior with regards to WCDMA. The ordering of the fields from #RFSTS are // the same as LTE up to the 16th field (for ME901C1-WW anyway). Drop into LTE parsing for ME910C1-WW. } else if((vParts.size() >= WCDMA_NETWORK_FORMAT) && (getName().find("ME910C1-WW") == std::string::npos)) { Json::Value jDebug; //Parse as WCDMA Network Format jDebug[KEY_PSC] = vParts[2]; jDebug[KEY_ECIO] = vParts[3]; jDebug[KEY_RSCP] = vParts[4]; jData[KEY_RSSIDBM] = vParts[5]; jData[KEY_LAC] = vParts[6]; jData[KEY_RAC] = vParts[7]; jDebug[KEY_TXPWR] = vParts[8]; jDebug[KEY_DRX] = vParts[9]; jDebug[KEY_MM] = vParts[10]; jDebug[KEY_RR] = vParts[11]; jDebug[KEY_NOM] = vParts[12]; if(vParts[13].size() != 0) { jDebug[KEY_BLER] = vParts[13]; } else { jDebug[KEY_BLER] = "000"; } jData[KEY_CID] = vParts[14]; jData[KEY_IMSI] = MTS::Text::strip(vParts[15], '"'); jData[KEY_NETWORK] = MTS::Text::strip(vParts[16], '"'); // Get the radio band given the channel (UARFCN) RadioBandMap radioBandMap(vParts[1], CellularRadio::VALUE_TYPE_CDMA); jData[KEY_ABND] = radioBandMap.getRadioBandName(); if(MTS::Text::parse(iValue, vParts[17]) && convertServiceDomainToString((SERVICEDOMAIN)iValue, sValue) == SUCCESS) { jDebug[KEY_SD] = sValue; } //Ignoring Active Set Values // - Number of Active Set (Maximum 6) // - UARFCN of n th active set // - PSC of n th active set // - Ec/Io of n th active Set jData[KEY_DEBUG] = jDebug; } else if(vParts.size() >= LTE_NETWORK_FORMAT) { Json::Value jDebug; //Parse as LTE Network Format // // MD: It is noticed that LTE Network format may vary depending on the firmware version: // // ,,,,,,[],,,,,,[],,, // Ex 1: #RFSTS: "310 260",2300,-98,-63,-14,AA06,,128,19,0,0501D02,"310260754792598","T-Mobile",3,4,197 // // ,,,,,,,[],,,,,,[],, // Ex 2: #RFSTS:"310 410",5780,-105,-73,-14,4603,255,,128,19,0,0000098,"310410536498694","AT&T",3,17 // #RFSTS:"311 480",1150,-96,-66,-9.0,bf35,FF,0,0,19,1,"2ED1B0E","311480148817753","Verizon",2,2,720000,10800 // #RFSTS:"310 410",2175,-120,-89,-17.5,4612,FF,0,0,19,1,"4E5E916","310410807276607","AT&T",3,4 // // Additional parameter in the second example shifts the rest of the parameters. Here we are trying to figure out // which format is currently produced based on field position which always has double quotation marks. // if (vParts[13].find("\"") != std::string::npos) { // parse the RAC and then remove it from the vector jData[KEY_RAC] = vParts[6]; vParts.erase(vParts.begin() + 6); } jDebug["rsrp"] = vParts[2]; jDebug[KEY_RSSIDBM] = vParts[3]; jDebug["rsrq"] = vParts[4]; jData["tac"] = vParts[5]; jDebug[KEY_TXPWR] = vParts[6]; jData[KEY_DRX] = vParts[7]; jDebug[KEY_MM] = vParts[8]; jDebug["rrc"] = vParts[9]; jData[KEY_CID] = MTS::Text::strip(vParts[10], '"'); jData[KEY_IMSI] = MTS::Text::strip(vParts[11], '"'); jData[KEY_NETWORK] = MTS::Text::strip(vParts[12], '"'); // Get the radio band given the channel (EARFCN) RadioBandMap radioBandMap(vParts[1], CellularRadio::VALUE_TYPE_LTE); jData[KEY_ABND] = radioBandMap.getRadioBandName(); jData[KEY_LAC] = queryLteLac(); if(MTS::Text::parse(iValue, vParts[13]) && convertServiceDomainToString((SERVICEDOMAIN)iValue, sValue) == SUCCESS) { jDebug[KEY_SD] = sValue; } jData[KEY_DEBUG] = jDebug; } printTrace("%s| Network Status:\n%s\n", getName().c_str(), jData.toStyledString().c_str()); return SUCCESS; } // Get the LAC for the LTE radio that's not in the #RFSTS response std::string TelitRadio::queryLteLac() { std::string CGREGstring; std::string originalCGREG; std::string result; CGREGstring = queryCGREGstring(); if (CGREGstring == 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 == RSP_ERROR) { result = CellularRadio::VALUE_UNKNOWN; } else { size_t start = CGREGstring.find(":") + 1; //Position right after "#RFSTS:" std::vector vParts = MTS::Text::split(MTS::Text::trim(CGREGstring.substr(start)), ","); if(vParts.size() < 3) { result = CellularRadio::VALUE_UNAVAILABLE; } else { result = MTS::Text::strip(vParts[2], '"'); } } setCGREG(originalCGREG); return result; } void TelitRadio::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]", getName().c_str(), value.c_str(), sCmd.c_str(), cmdResult.c_str()); } } std::string TelitRadio::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]", getName().c_str(), sCmd.c_str(), cmdResult.c_str()); return RSP_ERROR; } return cmdResult; } CellularRadio::CODE TelitRadio::setMdn(const Json::Value& jArgs) { printTrace("%s| Set MDN", getName().c_str()); if(!jArgs["mdn"].isString()) { return INVALID_ARGS; } std::string sCmd("AT#SNUM=1,\""); sCmd += jArgs["mdn"].asString() + "\""; std::string sResult = sendCommand(sCmd, DEFAULT_BAIL_STRINGS, 1000); size_t end = sResult.find(RSP_OK); if (end == std::string::npos) { printWarning("%s| Unable to set MDN for radio using command [%s]", getName().c_str(), sCmd.c_str()); return FAILURE; } return SUCCESS; } bool TelitRadio::getCarrierFromFirmware(const std::string& sFirmware, std::string& sCarrier) { // Telit Radios // H.ab.zyx => 3 Main Components // "H" = Hardware -> 15 = DE910 family, 18 = CE910 family, 12 = HE910 family // "a" = Hardware version // "b" = Software Major Version // "z" = is the product type, i.e. DUAL or SC // "y" = is the carrier variant // "x" = is the firmware version // Telit will do their best to keep the carrier variant as "0" for Sprint, "1" for Aeris, "2" for Verizon, and "3" for U.S. Cellular. const uint32_t CARRIER_INDEX = 1; //y in [zyx] bool bResult = false; std::vector vParts = MTS::Text::split(sFirmware, '.'); if(vParts.size() == 3) { //CDMA firmware version notation if(vParts[0] == "15" || vParts[0] == "18") { //DE910 or CE910 -> Good good std::string sID = vParts[2]; if(sID.size() == 3) { char cId = sID[CARRIER_INDEX]; //Good good if(cId == '0') { sCarrier = VALUE_CARRIER_SPRINT; bResult = true; } else if(cId == '1') { sCarrier = VALUE_CARRIER_AERIS; bResult = true; } else if(cId == '2') { sCarrier = VALUE_CARRIER_VERIZON; bResult = true; } else if(cId == '3') { sCarrier = VALUE_CARRIER_USCELLULAR; bResult = true; } } } } return bResult; } bool TelitRadio::getHardwareVersionFromFirmware(const std::string& sFirmware, std::string& sHardware) { // Telit Radios // H.ab.zyx => 3 Main Components // "H" = Hardware -> 15 = DE910 family, 18 = CE910 family, 12 = HE910 family // "a" = Hardware version // "b" = Software Major Version // "z" = is the product type, i.e. DUAL or SC // "y" = is the carrier variant // "x" = is the firmware version // Telit will do their best to keep the carrier variant as "0" for Sprint, "1" for Aeris, and "2" for Verizon. const uint32_t HARDWARE_INDEX = 0; //a in [ab] bool bResult = false; std::vector vParts = MTS::Text::split(sFirmware, '.'); if(vParts.size() == 3) { //GSM Hardware Version if(!(vParts[0] == "15" || vParts[0] == "18")) { //Not DE910 or CE910 -> Good good std::string sVersion = vParts[1]; if(sVersion.size() == 2) { sHardware = "1."; sHardware += sVersion[HARDWARE_INDEX]; bResult = true; } } } return bResult; }