summaryrefslogtreecommitdiff
path: root/src/MTS_IO_SequansRadio.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/MTS_IO_SequansRadio.cpp')
-rw-r--r--src/MTS_IO_SequansRadio.cpp553
1 files changed, 553 insertions, 0 deletions
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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <mts/MTS_IO_SequansRadio.h>
+
+#include <mts/MTS_Logger.h>
+#include <mts/MTS_Thread.h>
+#include <mts/MTS_Text.h>
+#include <mts/MTS_Timer.h>
+
+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<std::string> vParts = MTS::Text::split(sResult, ',');
+ int32_t iAccessTechnology;
+
+ // +COPS: <mode>[,<format>[,<oper>][,<Act>]]
+ 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<std::string> 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: [<mcc>,<mnc>,<cellId>,<pci>,<tac>,<dlBw>,<specialSubframeConfig>,<subframeAssignment>,<dlEarfcn>,<dlTxMode>,<band>]
+ICellularRadio::CODE SequansRadio::getNetworkStatusSQN(Json::Value& jData, Json::Value& jDebug) {
+ std::string sCmd;
+ std::string sResult;
+ std::string sPrefix;
+ std::vector<std::string> 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;
+ }
+
+ // <mcc>,<mnc>,<cellId>,<pci>,<tac>,<dlBw>,<specialSubframeConfig>,<subframeAssignment>,<dlEarfcn>,<dlTxMode>,<band>
+ // [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: <rxlev>,<ber>,<rscp>,<ecno>,<rsrq>,<rsrp>
+ICellularRadio::CODE SequansRadio::getNetworkStatusSignal(Json::Value& jData, Json::Value& jDebug) {
+ std::string sCmd;
+ std::string sResult;
+ std::string sPrefix;
+ std::vector<std::string> 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;
+ }
+
+ // <rxlev>,<ber>,<rscp>,<ecno>,<rsrq>,<rsrp>
+ // [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: <txPower>,<maxTxPower>
+ICellularRadio::CODE SequansRadio::getNetworkStatusTxPower(Json::Value& jData, Json::Value& jDebug) {
+ std::string sCmd;
+ std::string sResult;
+ std::string sPrefix;
+ std::vector<std::string> 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;
+ }
+ // <txPower>,<maxTxPower>
+ // [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<std::string> 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<std::string> 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<std::string>& 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<std::string> vCommands {
+ };
+
+ return vCommands;
+}