From c2481cf0773c1550fdb9a28d1a0b407c577ac34e Mon Sep 17 00:00:00 2001 From: Yevhen Mykhno Date: Tue, 31 Jan 2023 13:39:31 +0200 Subject: [GP-1733] mPower R.6.3.X: L6G1 Support - libmts-io, radio-cmd, radio-query Implementing CB610L radio modem support --- src/MTS_IO_SequansRadio.cpp | 553 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 553 insertions(+) create mode 100644 src/MTS_IO_SequansRadio.cpp (limited to 'src/MTS_IO_SequansRadio.cpp') diff --git a/src/MTS_IO_SequansRadio.cpp b/src/MTS_IO_SequansRadio.cpp new file mode 100644 index 0000000..f3b32fb --- /dev/null +++ b/src/MTS_IO_SequansRadio.cpp @@ -0,0 +1,553 @@ +/* + * 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()); + printTrace("%s| Network Status:\n%s\n", getName().c_str(), jData.toStyledString().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()); + printTrace("%s| Network Status:\n%s\n", getName().c_str(), jData.toStyledString().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()); + printTrace("%s| Network Status:\n%s\n", getName().c_str(), jData.toStyledString().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); + printTrace("%s| Network Status:\n%s\n", getName().c_str(), jData.toStyledString().c_str()); + 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); + printTrace("%s| Network Status:\n%s\n", getName().c_str(), jData.toStyledString().c_str()); + 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; + int iValue; + 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()); + printTrace("%s| Network Status:\n%s\n", getName().c_str(), jData.toStyledString().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()); + printTrace("%s| Network Status:\n%s\n", getName().c_str(), jData.toStyledString().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][%d]", getName().c_str(), sCmd.c_str(), iValue); + printTrace("%s| Network Status:\n%s\n", getName().c_str(), jData.toStyledString().c_str()); + 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; + + getCommonNetworkStats(jData); + getNetworkStatusSQN(jData, jDebug); + getNetworkStatusSignal(jData, jDebug); + getNetworkStatusTxPower(jData, jDebug); + + if (jData.isMember(ICellularRadio::KEY_RSSIDBM)) { + jDebug[ICellularRadio::KEY_RSSIDBM] = jData[ICellularRadio::KEY_RSSIDBM]; + } + + if (SUCCESS == getImsi(sResult)) { + jData[ICellularRadio::KEY_IMSI] = sResult; + } + + if (SUCCESS == getNetwork(sResult)) { + jData[ICellularRadio::KEY_NETWORK] = sResult; + } + + 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; +} + +const std::vector& SequansRadio::getDiagCommands(bool) { + // TODO: Fill the list of commands in scope of the "L6G1 - Cellular Diagnostics" feature + + // Declare as static to initialize only when used, but cache the results. + const static std::vector vCommands { + }; + + return vCommands; +} -- cgit v1.2.3