Skip to content
This repository has been archived by the owner on Nov 11, 2024. It is now read-only.

Commit

Permalink
BREAKING CHANGE for cellular: report RAT-appropriate SNR equivalent w…
Browse files Browse the repository at this point in the history
…here available. (#835)

Up to now uCellInfoGetSnrDb() has reported SNR by determining the ratio of RSRP / (RSSI - RSRP) in dB for all RATs. However, that is really only appropriate for 2G; for 3G and 4G the appropriate SNR equivalent value, as reported directly by the module, is now returned. For 3G this is EC/N0, while for Cat-M1/NB/LTE it is the signal to interference and noise ratio (SINR).

EXISTING CELLULAR USERS may experience quite different [but now correct] SNR results for Cat-M1, NB, LTE and UTRAN.
  • Loading branch information
RobMeades authored Feb 24, 2023
1 parent c585e45 commit 8305bbf
Show file tree
Hide file tree
Showing 4 changed files with 103 additions and 14 deletions.
5 changes: 4 additions & 1 deletion cell/api/u_cell_info.h
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,10 @@ int32_t uCellInfoGetRxQual(uDeviceHandle_t cellHandle);
* uCellInfoGetRsrpDbm() and uCellInfoGetRsrqDb() in that a
* pointer must be passed in to obtain the result. This is
* because negative, positive and zero values for SNR are valid.
* SNR is RSRP / (RSSI - RSRP) and so if RSSI and RSRP are the
* For Cat-M1/NB/LTE the signal to interference and noise
* ratio (SINR) reported directly by the module is returned.
* For 3G, EC/N0 is reported. For 2G, SNR is
* RSRP / (RSSI - RSRP) and if RSSI and RSRP are the
* same a maximal integer value will be returned.
* SNR may not be available unless the module has successfully
* registered with the cellular network.
Expand Down
110 changes: 97 additions & 13 deletions cell/src/u_cell_info.c
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
#include "stdbool.h"
#include "string.h" // strlen()
#include "time.h" // struct tm
#include "ctype.h" // isdigit()

#include "u_cfg_sw.h"

Expand Down Expand Up @@ -134,6 +135,21 @@ static int32_t rssiUtranToDbm(int32_t rssi)
return rssiDbm;
}

// Convert the UTRAN ecnoLev number to dB.
// 0: less than -24 dB
// 1..48: -24 dB to 0 dB in 0.5 dB steps
// 49: less than 0 dB
// Returns 0x7FFFFFFF if the number is not known.
static int32_t ecnoLevToDb(int32_t ecnoLev)
{
int32_t ecnoDb = 0x7FFFFFFF;

if ((ecnoLev >= 0) && (ecnoLev <= 49)) {
ecnoDb = -((ecnoLev - 49) >> 2);
}

return ecnoDb;
}

// Get an ID string from the cellular module.
static int32_t getString(uAtClientHandle_t atHandle,
Expand Down Expand Up @@ -172,6 +188,36 @@ static int32_t getString(uAtClientHandle_t atHandle,
return errorCodeOrSize;
}

// Get SINR as an integer from a decimal (e.g -13.75) in a string,
// or 0x7FFFFFFF if not known
static int32_t getSinr(const char *pStr, int32_t divisor)
{
int32_t sinrDb = 0x7FFFFFFF;
int32_t x;
char *pTmp;

x = strtol(pStr, &pTmp, 10);
// 255 means "not present/known"
if (x != 255) {
sinrDb = x;
if (*pTmp == '.') {
// Round based on mantissa
pTmp++;
if (isdigit((int32_t) *pTmp) && (*pTmp >= 0x35)) {
if (x >= 0) {
sinrDb++;
} else {
sinrDb--;
}
}
}
sinrDb += divisor / 2; // This to round to the nearest integer
sinrDb /= divisor;
}

return sinrDb;
}

// Fill in the radio parameters the AT+CSQ way
static int32_t getRadioParamsCsq(uAtClientHandle_t atHandle,
uCellPrivateRadioParameters_t *pRadioParameters)
Expand Down Expand Up @@ -207,6 +253,7 @@ static int32_t getRadioParamsUcged2SaraR5(uAtClientHandle_t atHandle,
uCellPrivateRadioParameters_t *pRadioParameters)
{
int32_t x;
char buffer[10]; // More than enough room for an SNIR reading, e.g. 13.75, with a terminator

// +UCGED: 2
// <rat>,<svc>,<MCC>,<MNC>
Expand Down Expand Up @@ -244,6 +291,12 @@ static int32_t getRadioParamsUcged2SaraR5(uAtClientHandle_t atHandle,
// negative return value.
pRadioParameters->rsrqDb = rsrqToDb(x);
}
// SINR is element 13, directly in dB, a decimal number
// with a mantissa, 255 if unknown.
x = uAtClientReadString(atHandle, buffer, sizeof(buffer), false);
if (x > 0) {
pRadioParameters->snrDb = getSinr(buffer, 1);
}
uAtClientResponseStop(atHandle);

return uAtClientUnlock(atHandle);
Expand Down Expand Up @@ -329,6 +382,12 @@ static int32_t getRadioParamsUcged2SaraR422(uAtClientHandle_t atHandle,
pRadioParameters->rsrpDbm = x;
pRadioParameters->rsrqDb = y;
}
// SINR is element 9, encoded in 1/5ths of a dB where
// 0 is -20 dB and the maximum is 250 (30 dB)
x = uAtClientReadInt(atHandle);
if (x >= 0) {
pRadioParameters->snrDb = (x - (20 * 5)) / 5;
}
}

uAtClientResponseStop(atHandle);
Expand All @@ -344,7 +403,7 @@ static int32_t getRadioParamsUcged2LaraR6(uAtClientHandle_t atHandle,
int32_t skipParameters = 2;
int32_t x;
int32_t y;

char buffer[10]; // More than enough room for an SNIR reading, e.g. 13.75, with a terminator
// The formats are RAT dependent as follows:
//
// 2G:
Expand All @@ -370,7 +429,7 @@ static int32_t getRadioParamsUcged2LaraR6(uAtClientHandle_t atHandle,
//
// +UCGED: 2
// 4,<svc>,<MCC>,<MNC>
// <EARFCN>,<Lband>,<ul_BW>,<dl_BW>,<TAC>,<LcellId>,<P-CID>,<mTmsi>,<mmeGrId>,<mmeCode>,<RSRP>,<RSRQ>... etc.
// <EARFCN>,<Lband>,<ul_BW>,<dl_BW>,<TAC>,<LcellId>,<P-CID>,<mTmsi>,<mmeGrId>,<mmeCode>,<RSRP>,<RSRQ>,<Lsinr>... etc.
// e.g.
// 4,0,001,01
// 2525,5,25,50,2b67,69f6bc7,111,00000000,ffff,ff,67,19,0.00,255,255,255,67,11,255,0,255,255,0,0
Expand Down Expand Up @@ -410,6 +469,8 @@ static int32_t getRadioParamsUcged2LaraR6(uAtClientHandle_t atHandle,
uAtClientSkipParameters(atHandle, 4);
// Read <rssi> and convert it to dBm
pRadioParameters->rssiDbm = rssiUtranToDbm(uAtClientReadInt(atHandle));
// Read <ecn0_lev> and convert it to dB
pRadioParameters->snrDb = ecnoLevToDb(uAtClientReadInt(atHandle));
// Ignore the rest
break;
case 4:
Expand All @@ -433,6 +494,12 @@ static int32_t getRadioParamsUcged2LaraR6(uAtClientHandle_t atHandle,
pRadioParameters->rsrpDbm = x;
pRadioParameters->rsrqDb = y;
}
// SINR is element 13, directly in tenths of a dB, a
// decimal number with a mantissa, 255 if unknown.
x = uAtClientReadString(atHandle, buffer, sizeof(buffer), false);
if (x > 0) {
pRadioParameters->snrDb = getSinr(buffer, 10);
}
break;
default:
break;
Expand Down Expand Up @@ -562,6 +629,9 @@ int32_t uCellInfoRefreshRadioParameters(uDeviceHandle_t cellHandle)
uPortLog(" RxQual: %d\n", pRadioParameters->rxQual);
uPortLog(" cell ID: %d\n", pRadioParameters->cellId);
uPortLog(" EARFCN: %d\n", pRadioParameters->earfcn);
if (pRadioParameters->snrDb != 0x7FFFFFFF) {
uPortLog(" SNR: %d\n", pRadioParameters->snrDb);
}
} else {
uPortLog("U_CELL_INFO: unable to refresh radio parameters.\n");
}
Expand Down Expand Up @@ -720,6 +790,7 @@ int32_t uCellInfoGetSnrDb(uDeviceHandle_t cellHandle, int32_t *pSnrDb)
int32_t errorCode = (int32_t) U_ERROR_COMMON_NOT_INITIALISED;
uCellPrivateInstance_t *pInstance;
uCellPrivateRadioParameters_t *pRadioParameters;
uCellNetRat_t rat;

if (gUCellPrivateMutex != NULL) {

Expand All @@ -729,17 +800,30 @@ int32_t uCellInfoGetSnrDb(uDeviceHandle_t cellHandle, int32_t *pSnrDb)
errorCode = (int32_t) U_ERROR_COMMON_INVALID_PARAMETER;
if ((pInstance != NULL) && (pSnrDb != NULL)) {
pRadioParameters = &(pInstance->radioParameters);
errorCode = (int32_t) U_CELL_ERROR_VALUE_OUT_OF_RANGE;
// SNR = RSRP / (RSSI - RSRP).
if ((pRadioParameters->rssiDbm != 0) &&
(pRadioParameters->rssiDbm <= pRadioParameters->rsrpDbm)) {
*pSnrDb = INT_MAX;
errorCode = (int32_t) U_ERROR_COMMON_SUCCESS;
} else if ((pRadioParameters->rssiDbm != 0) && (pRadioParameters->rsrpDbm != 0)) {
int32_t ix = pRadioParameters->rssiDbm - (pRadioParameters->rsrpDbm + 1);
if (ix >= 0) {
const signed char snrLut[] = {6, 2, 0, -2, -3, -5, -6, -7, -8, -10};
*pSnrDb = (ix < (int32_t) sizeof(snrLut)) ? snrLut[ix] : (- ix - 1);
errorCode = (int32_t) U_ERROR_COMMON_NOT_FOUND;
rat = uCellPrivateGetActiveRat(pInstance);
if ((rat == U_CELL_NET_RAT_GSM_GPRS_EGPRS) ||
(rat == U_CELL_NET_RAT_EGPRS)) {
errorCode = (int32_t) U_CELL_ERROR_VALUE_OUT_OF_RANGE;
// SNR = RSRP / (RSSI - RSRP).
if ((pRadioParameters->rssiDbm != 0) &&
(pRadioParameters->rssiDbm <= pRadioParameters->rsrpDbm)) {
*pSnrDb = INT_MAX;
errorCode = (int32_t) U_ERROR_COMMON_SUCCESS;
} else if ((pRadioParameters->rssiDbm != 0) && (pRadioParameters->rsrpDbm != 0)) {
int32_t ix = pRadioParameters->rssiDbm - (pRadioParameters->rsrpDbm + 1);
if (ix >= 0) {
const signed char snrLut[] = {6, 2, 0, -2, -3, -5, -6, -7, -8, -10};
*pSnrDb = (ix < (int32_t) sizeof(snrLut)) ? snrLut[ix] : (- ix - 1);
errorCode = (int32_t) U_ERROR_COMMON_SUCCESS;
}
}
} else {
if (pRadioParameters->snrDb != 0x7FFFFFFF) {
// If we have a stored SNIR value that we've been
// able to read directly out of the module, then
// report that
*pSnrDb = pRadioParameters->snrDb;
errorCode = (int32_t) U_ERROR_COMMON_SUCCESS;
}
}
Expand Down
1 change: 1 addition & 0 deletions cell/src/u_cell_private.c
Original file line number Diff line number Diff line change
Expand Up @@ -607,6 +607,7 @@ void uCellPrivateClearRadioParameters(uCellPrivateRadioParameters_t *pParameters
pParameters->rsrqDb = 0x7FFFFFFF;
pParameters->cellId = -1;
pParameters->earfcn = -1;
pParameters->snrDb = 0x7FFFFFFF;
}

// Clear the dynamic parameters of an instance,
Expand Down
1 change: 1 addition & 0 deletions cell/src/u_cell_private.h
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,7 @@ typedef struct {
int32_t rxQual; /**< The RxQual of the serving cell. */
int32_t cellId; /**< The cell ID of the serving cell. */
int32_t earfcn; /**< The EARFCN of the serving cell. */
int32_t snrDb; /**< The SINR as reported by the module (LTE only). */
} uCellPrivateRadioParameters_t;

/** Structure to hold a network name, MCC/MNC and RAT
Expand Down

0 comments on commit 8305bbf

Please sign in to comment.