diff options
| author | Jeff Hatch <jhatch@multitech.com> | 2023-01-31 13:54:52 -0600 | 
|---|---|---|
| committer | Jeff Hatch <jhatch@multitech.com> | 2023-01-31 13:54:52 -0600 | 
| commit | b504ed673fa55ae1a429fd468079ee8c16c4a04a (patch) | |
| tree | 620e1d7509ddfe0e9b7102485ce01a0ff4549964 /src/MTS_IO_SequansRadio.cpp | |
| parent | 430506fb7757d6736988d75c8ea53c85f6c97da9 (diff) | |
| parent | 23d702dfaaa6ef20a02e410a09ae6f67e967464f (diff) | |
| download | libmts-io-b504ed673fa55ae1a429fd468079ee8c16c4a04a.tar.gz libmts-io-b504ed673fa55ae1a429fd468079ee8c16c4a04a.tar.bz2 libmts-io-b504ed673fa55ae1a429fd468079ee8c16c4a04a.zip | |
Merge branch 'ym/GP-1733/add_sequans_support' into 'master'
[GP-1733] mPower R.6.3.X: L6G1 Support
See merge request !53
Diffstat (limited to 'src/MTS_IO_SequansRadio.cpp')
| -rw-r--r-- | src/MTS_IO_SequansRadio.cpp | 553 | 
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; +} | 
