/* * Copyright (C) 2023 by Multi-Tech Systems * * This file is part of libmts-io. * * libmts-io is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * libmts-io is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with libmts-io. If not, see . * */ #include #include #include #include #include using namespace MTS::IO; bool SequansRadio::resetRadio(uint32_t iTimeoutMillis) { printInfo("%s| Rebooting radio", getName().c_str()); if (sendBasicCommand("AT^RESET") == SUCCESS) { if (iTimeoutMillis > 5000) { MTS::Thread::sleep(5000); iTimeoutMillis -= 5000; } return resetConnection(iTimeoutMillis); } return false; } ICellularRadio::CODE SequansRadio::getVendorFirmware(std::string& sVendorFirmware) { return getFirmware(sVendorFirmware); } ICellularRadio::CODE SequansRadio::getModel(std::string& sModel) { printTrace("%s| Get Model", getName().c_str()); //Always returns SUCCESS because the model should be m_sName sModel = getName(); std::string sCmd("AT+CGMM"); std::string sResult = sendCommand(sCmd); if (sResult.find("OK") == std::string::npos) { printWarning("%s| Unable to get model from radio. Returning [%s]", getName().c_str(), getName().c_str()); return SUCCESS; } else { sModel = extractModelFromResult(sResult); if (sModel.size() == 0) { printWarning("%s| Unable to get model from radio. Returning [%s]", getName().c_str(), getName().c_str()); return SUCCESS; } } printDebug("%s| Extracted [%s] from [%s] query", getName().c_str(), sModel.c_str(), sCmd.c_str()); if (sModel != getName()) { printWarning("%s| Model identified [%s] does not match expected [%s]. Returning [%s]", getName().c_str(), sModel.c_str(), getName().c_str(), sModel.c_str()); } return SUCCESS; } ICellularRadio::CODE SequansRadio::getIccid(std::string& sIccid) { printTrace("%s| Get ICCID", getName().c_str()); const int iEFiccidId = 0x2FE2; const uint8_t iOffsetHigh = 0; const uint8_t iOffsetLow = 0; const uint8_t iNumBytes = 10; CODE rc; std::string sEFiccidContent; rc = simAccessReadBinary(iEFiccidId, iOffsetLow, iOffsetHigh, iNumBytes, sEFiccidContent); if (rc != SUCCESS) { printError("%s| Failed to determine the SIM ICCID", getName().c_str()); return rc; } int iEFiccidLen = sEFiccidContent.size(); if (iEFiccidLen != 20) { printError("%s| Invalid length of the raw ICCID value: expected [20], actual: [%d]", getName().c_str(), iEFiccidLen); return FAILURE; } // sEFiccidContent is a raw Binary-Coded Decimal string with swapped nibbles. for (int i = 0; i < iEFiccidLen; i+=2) { std::string sPart = sEFiccidContent.substr(i, 2); std::swap(sPart[0], sPart[1]); sIccid.append(sPart); } printDebug("%s| Got ICCID [%s]", getName().c_str(), sIccid.c_str()); return SUCCESS; } ICellularRadio::CODE SequansRadio::getService(std::string& sService) { printTrace("%s| Get Service", getName().c_str()); sService = ICellularRadio::VALUE_NOT_SUPPORTED; std::string sCmd("AT+COPS?"); std::string sPrefix("+COPS:"); std::string sResult; CODE rc; rc = sendBasicQuery(sCmd, sPrefix, sResult, 100); if (SUCCESS != rc) { printWarning("%s| Unable to get Service from radio using command [%s]", getName().c_str(), sCmd.c_str()); return rc; } std::vector vParts = MTS::Text::split(sResult, ','); int32_t iAccessTechnology; // +COPS: [,[,][,]] if (vParts.size() < 4 || !MTS::Text::parse(iAccessTechnology, vParts[3])) { printWarning("%s| Unable to get Service from radio using command [%s]", getName().c_str(), sCmd.c_str()); return FAILURE; } switch(iAccessTechnology) { case 0 : sService = "GPRS" ; break; // GSM case 1 : sService = "GPRS" ; break; // GSM Compact case 2 : sService = "WCDMA" ; break; // UTRAN case 3 : sService = "EGPRS" ; break; // GSM W/EGPRS case 4 : sService = "HSDPA" ; break; // UTRAN W/HSDPA case 5 : sService = "WCDMA" ; break; // UTRAN W/HSUPA case 6 : sService = "HSDPA" ; break; // UTRAN W/HSDPA and HSUPA case 7 : sService = "LTE" ; break; // E-UTRAN case 8 : sService = "EGPRS" ; break; // EC-GSM-IoT case 9 : sService = "LTE" ; break; // E-UTRAN (NB-S1 mode) default: sService = ICellularRadio::VALUE_UNKNOWN; break; } printDebug("%s| Service ID: [%d][%s]", getName().c_str(), iAccessTechnology, sService.c_str()); return SUCCESS; } ICellularRadio::CODE SequansRadio::getNetwork(std::string& sNetwork) { printTrace("%s| Get Network", getName().c_str()); sNetwork = ICellularRadio::VALUE_NOT_SUPPORTED; std::string sCmd("AT+COPS?"); std::string sPrefix("+COPS:"); std::string sResult; CODE rc; rc = sendBasicQuery(sCmd, sPrefix, sResult, 100); if (SUCCESS != rc) { printWarning("%s| Unable to get network name from radio using command [%s]", getName().c_str(), sCmd.c_str()); return rc; } std::vector vParts = MTS::Text::split(sResult, ","); if (vParts.size() > 3) { const std::string sValue = vParts[2]; // +COPS: 0,2,"315010",7 // ^start ^end size_t start = sValue.find("\"") + 1; size_t end = sValue.find("\"", start); sNetwork = sValue.substr(start, end-start); } else { sNetwork = ""; // Not connected to any network } return SUCCESS; } // AT+SQNQRCCI? -- This command queries and reports several information from camped cell. // +SQNQRCCI: [,,,,,,,,,,] ICellularRadio::CODE SequansRadio::getNetworkStatusSQN(Json::Value& jData, Json::Value& jDebug) { std::string sCmd; std::string sResult; std::string sPrefix; std::vector vParts; CODE rc; sCmd = "AT+SQNQRCCI?"; sPrefix = "+SQNQRCCI:"; rc = sendBasicQuery(sCmd, sPrefix, sResult, 200); if (SUCCESS != rc) { printDebug("%s| Network Status command returned unexpected response: [%s][%s]", getName().c_str(), sCmd.c_str(), sResult.c_str()); return rc; } // ,,,,,,,,,, // [0] [1] [2] [3] [4] [5] [6] [7] [8] [9] [10] vParts = MTS::Text::split(sResult, ","); if (vParts.size() < 11) { 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 FAILURE; } jData[ICellularRadio::KEY_MCC] = vParts[0]; jData[ICellularRadio::KEY_MNC] = vParts[1]; jData[ICellularRadio::KEY_CID] = vParts[2]; jData["tac"] = vParts[4]; jData[ICellularRadio::KEY_CHANNEL] = vParts[8]; jData[ICellularRadio::KEY_ABND] = "EUTRAN BAND" + vParts[10]; // concat string to convert to common format return SUCCESS; } // AT+CESQ? -- Execution command returns received signal quality parameters. // +CESQ: ,,,,, ICellularRadio::CODE SequansRadio::getNetworkStatusSignal(Json::Value& jData, Json::Value& jDebug) { std::string sCmd; std::string sResult; std::string sPrefix; std::vector vParts; CODE rc; sCmd = "AT+CESQ"; sPrefix = "+CESQ:"; rc = sendBasicQuery(sCmd, sPrefix, sResult, 200); if (SUCCESS != rc) { printDebug("%s| Network Status command returned unexpected response: [%s][%s]", getName().c_str(), sCmd.c_str(), sResult.c_str()); return rc; } // ,,,,, // [0] [1] [2] [3] [4] [5] vParts = MTS::Text::split(sResult, ","); if (vParts.size() < 6) { printDebug("%s| Network Status command response is an unknown format: [%s][%s]", getName().c_str(), sCmd.c_str(), sResult.c_str()); return FAILURE; } int iValue; if (MTS::Text::parse(iValue, vParts[5])) { do { if (255 == iValue) { break; // invalid, not known or not detectable } if (iValue > 97 || iValue < 0) { printDebug("%s| Network Status command returned unexpected value of RSRP: [%s][%d]", getName().c_str(), sCmd.c_str(), iValue); break; } // converting value to rsrp dBm according to documentation jDebug["rsrp"] = std::to_string(iValue - 141); } while (false); } if (MTS::Text::parse(iValue, vParts[4])) { do { if (255 == iValue) { break; // invalid, not known or not detectable } if (iValue > 34 || iValue < 0) { printDebug("%s| Network Status command returned unexpected value of RSRQ: [%s][%d]", getName().c_str(), sCmd.c_str(), iValue); break; } // converting value to rsrq in dBm according to documentation jDebug["rsrq"] = std::to_string((iValue-40.0)/2.0); } while (false); } return SUCCESS; } // AT+SQNQRUP? -- This command queries and reports the uplink power of the current LTE network. // +SQNQRUP: , ICellularRadio::CODE SequansRadio::getNetworkStatusTxPower(Json::Value& jData, Json::Value& jDebug) { std::string sCmd; std::string sResult; std::string sPrefix; std::vector vParts; CODE rc; sCmd = "AT+SQNQRUP?"; sPrefix = "+SQNQRUP:"; // the key must be present jDebug[ICellularRadio::KEY_TXPWR] = ""; rc = sendBasicQuery(sCmd, sPrefix, sResult, 200); if (SUCCESS != rc) { printDebug("%s| Network Status command returned unexpected response: [%s][%s]", getName().c_str(), sCmd.c_str(), sResult.c_str()); return rc; } // , // [0] [1] vParts = MTS::Text::split(sResult, ","); if (vParts.size() < 2) { printDebug("%s| Network Status command response is an unknown format: [%s][%s]", getName().c_str(), sCmd.c_str(), sResult.c_str()); return FAILURE; } double fTxPow; if (MTS::Text::parse(fTxPow, vParts[0])) { do { if (-21474836.0 == fTxPow) { break; // invalid, not known or not detectable } if (fTxPow < -255 || fTxPow > 99) { printDebug("%s| Network Status command returned unexpected value of txPower: [%s][%f]", getName().c_str(), sCmd.c_str(), fTxPow); break; } jDebug[ICellularRadio::KEY_TXPWR] = vParts[0]; } while (false); } return SUCCESS; } ICellularRadio::CODE SequansRadio::getNetworkStatus(Json::Value& jData) { printTrace("%s| Get Network Status", getName().c_str()); // NOTE: Only the LTE radios from Sequans are supported at the moment Json::Value jDebug; std::string sResult; CODE rcMainStatus; getCommonNetworkStats(jData); rcMainStatus = getNetworkStatusSQN(jData, jDebug); getNetworkStatusSignal(jData, jDebug); getNetworkStatusTxPower(jData, jDebug); if (SUCCESS == getImsi(sResult)) { jData[ICellularRadio::KEY_IMSI] = sResult; } if (SUCCESS == getNetwork(sResult)) { jData[ICellularRadio::KEY_NETWORK] = sResult; } // Fill the debug information only if the main tower information was fetched successfully. if (SUCCESS == rcMainStatus) { jDebug[ICellularRadio::KEY_RSSIDBM] = jData[ICellularRadio::KEY_RSSIDBM]; jDebug[ICellularRadio::KEY_SD] = "PS"; jData[ICellularRadio::KEY_DEBUG] = jDebug; } printTrace("%s| Network Status:\n%s\n", getName().c_str(), jData.toStyledString().c_str()); return SUCCESS; } ICellularRadio::CODE MTS::IO::SequansRadio::convertSignalStrengthTodBm(const int32_t &iRssi, int32_t &iDBm) { const int dbmSteps = 2; const int minValue = -113; const int maxValue = -51; const int rssiOffset = 0; int rawDbm; if (iRssi < 0 || iRssi > 99) { return FAILURE; // invalid, not known or not detectable } rawDbm = minValue + ((iRssi - rssiOffset) * dbmSteps); iDBm = std::min(maxValue, rawDbm); return SUCCESS; } ICellularRadio::CODE SequansRadio::convertdBmToSignalStrength(const int32_t& iDBm, int32_t& iRssi) { const int dbmSteps = 2; const int minValue = -113; const int rssiOffset = 0; if (iDBm < -113) { iRssi = 0; } else if (iDBm > -51) { iRssi = 31; } else { iRssi = ((iDBm - minValue) / dbmSteps) + rssiOffset; } return SUCCESS; } ICellularRadio::CODE SequansRadio::setMdn(const Json::Value& jArgs) { printTrace("%s| Set MDN", getName().c_str()); return NOT_APPLICABLE; } ICellularRadio::CODE SequansRadio::setUeModeOfOperation(ICellularRadio::UE_MODES_OF_OPERATION mode) { printTrace("%s| Set UE Mode Of Operation", getName().c_str()); std::string sValue; switch (mode) { case ICellularRadio::UE_MODES_OF_OPERATION::PS_MODE1: sValue = "3"; break; case ICellularRadio::UE_MODES_OF_OPERATION::PS_MODE2: sValue = "0"; break; case ICellularRadio::UE_MODES_OF_OPERATION::CS_PS_MODE1: sValue = "1"; break; case ICellularRadio::UE_MODES_OF_OPERATION::CS_PS_MODE2: sValue = "2"; break; default: printError("%s| Set UE Mode Of Operation: invalid argument", getName().c_str()); return INVALID_ARGS; } const int dTimeout = 1000; // ms const std::string sCmd = "AT+CEMODE=" + sValue; return sendBasicCommand(sCmd, dTimeout); } ICellularRadio::CODE SequansRadio::getUeModeOfOperation(ICellularRadio::UE_MODES_OF_OPERATION& mode) { printTrace("%s| Get UE Mode Of Operation", getName().c_str()); std::string sCmd = "AT+CEMODE?"; std::string sPrefix = "+CEMODE:"; std::string sResult; if (SUCCESS != sendBasicQuery(sCmd, sPrefix, sResult, 1000)) { printError("%s| Unable to get UE Mode Of Operation from radio using command [%s]", getName().c_str(), sCmd.c_str()); return FAILURE; } uint8_t uiValue; if (!MTS::Text::parse(uiValue, sResult)) { printError("%s| Unable to parse CEMODE from response [%s]", getName().c_str(), sResult.c_str()); return FAILURE; } CODE rc; switch (uiValue) { case 0: mode = ICellularRadio::UE_MODES_OF_OPERATION::PS_MODE2; rc = SUCCESS; break; case 1: mode = ICellularRadio::UE_MODES_OF_OPERATION::CS_PS_MODE1; rc = SUCCESS; break; case 2: mode = ICellularRadio::UE_MODES_OF_OPERATION::CS_PS_MODE2; rc = SUCCESS; break; case 3: mode = ICellularRadio::UE_MODES_OF_OPERATION::PS_MODE1; rc = SUCCESS; break; default: printError("%s| Unable to parse CEMODE from response [%s]", getName().c_str(), sResult.c_str()); mode = ICellularRadio::UE_MODES_OF_OPERATION::UNKNOWN_MODE; rc = FAILURE; break; } return rc; } ICellularRadio::CODE SequansRadio::setRxDiversity(const Json::Value& jArgs) { return NOT_APPLICABLE; } const std::vector SequansRadio::getRegistrationCommands() { return { "CEREG" }; } SequansRadio::SequansRadio(const std::string& sName, const std::string& sRadioPort) : CellularRadio (sName, sRadioPort) { } ICellularRadio::CODE SequansRadio::getIsSimInserted(bool& bData) { printTrace("%s| Get SIM insertion status", getName().c_str()); // there is no command to check SIM presence return NOT_APPLICABLE; } ICellularRadio::CODE SequansRadio::getSimLockAttempts(int& iAttemptsPin, int& iAttemptsPuk) { printTrace("%s| Get SIM unlock attempts left", getName().c_str()); CODE rc = FAILURE; rc = getSimLockAttemptsByType("SIM PIN", iAttemptsPin); if (rc != SUCCESS) { return rc; } rc = getSimLockAttemptsByType("SIM PUK", iAttemptsPuk); if (rc != SUCCESS) { return rc; } return SUCCESS; } ICellularRadio::CODE SequansRadio::getSimLockAttemptsByType(const std::string& sArg, int& iAttempts) { std::string sCmd("AT+CPINR"); std::string sPrefix("+CPINR:"); std::string sResult; int iValue; CODE rc = FAILURE; sCmd += "=\""; sCmd += sArg; sCmd += "\""; printDebug("%s| Sending SIM lock attempt query [%s]", getName().c_str(), sCmd.c_str()); rc = sendBasicQuery(sCmd, sPrefix, sResult, 500); if (SUCCESS != rc) { return rc; } std::vector vParts = MTS::Text::split(sResult, ','); if (vParts.size() < 3 || ! MTS::Text::parse(iValue, vParts[1])) { printWarning("%s| Unable to parse SIM unlock attempts from response [%s]", getName().c_str(), sResult.c_str()); return FAILURE; } iAttempts = iValue; return SUCCESS; } std::vector SequansRadio::getSupportedPdpContextAuthTypes() const { return { VALUE_PDP_CONTEXT_AUTH_TYPE_NONE, VALUE_PDP_CONTEXT_AUTH_TYPE_PAP, VALUE_PDP_CONTEXT_AUTH_TYPE_CHAP }; } ICellularRadio::CODE SequansRadio::isPdpContextAuthSupported(bool& isSupported) { isSupported = false; return SUCCESS; } ICellularRadio::CODE SequansRadio::fillPdpContextAuthFields(Json::Value& jData) { return NOT_APPLICABLE; } ICellularRadio::CODE SequansRadio::setPdpContextAuth(const PdpContextInfo& pdpContext) { return NOT_APPLICABLE; } const std::vector& SequansRadio::getDiagCommands(bool) { // Declare as static to initialize only when used, but cache the results. const static std::vector vCommands { // Radio model and firmware: "AT+CGMI", "AT+CGMM", "AT+CGMR", "ATI1", "at!=showVersion", // All carrier profiles that are supported: "AT+SQNCTM=?", // Current operator profile on the radio side: "AT+SQNCTM?", "AT+CGSN", // SIM card information: "AT+CRSM=176,12258,0,0,0", "AT+CPIN?", "AT+CPINR=\"SIM PIN\"", "AT+CPINR=\"SIM PUK\"", // Operating mode of the radio: "AT+CFUN?", "AT^AUTOATT?", "AT+SQNAUTOCONNECT?", // Low-level network settings: "AT+WS46?", "AT+CEMODE?", // Data connection configuration: "AT+CGDCONT?", "AT+CGAUTH?", // Registration and connection to the tower: "AT+CSQ", "AT+COPS?", "AT+CEREG?", "AT+SQNMONI=0", "AT+SQNQRCCI?", "AT+CESQ", "AT+SQNQRUP?", // Data connection status: "AT+CGACT?", "AT+CGCONTRDP=1", "AT+CGCONTRDP=2", "AT+CGCONTRDP=3" }; return vCommands; } ICellularRadio::CODE SequansRadio::setTimeFormat() { // AT+CSDF does not work on CB610L. Also, since CB610L does not return the correct time anyway. return NOT_APPLICABLE; }