From 8305bbfcf1ff249f8bc61eb7946ff40207d98bfe Mon Sep 17 00:00:00 2001 From: Rob Meades Date: Fri, 24 Feb 2023 17:48:56 +0000 Subject: [PATCH] BREAKING CHANGE for cellular: report RAT-appropriate SNR equivalent where 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. --- cell/api/u_cell_info.h | 5 +- cell/src/u_cell_info.c | 110 +++++++++++++++++++++++++++++++++----- cell/src/u_cell_private.c | 1 + cell/src/u_cell_private.h | 1 + 4 files changed, 103 insertions(+), 14 deletions(-) diff --git a/cell/api/u_cell_info.h b/cell/api/u_cell_info.h index 9e782148..062e458b 100755 --- a/cell/api/u_cell_info.h +++ b/cell/api/u_cell_info.h @@ -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. diff --git a/cell/src/u_cell_info.c b/cell/src/u_cell_info.c index bca2bbc3..a1cb4607 100755 --- a/cell/src/u_cell_info.c +++ b/cell/src/u_cell_info.c @@ -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" @@ -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, @@ -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) @@ -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 // ,,, @@ -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); @@ -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); @@ -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: @@ -370,7 +429,7 @@ static int32_t getRadioParamsUcged2LaraR6(uAtClientHandle_t atHandle, // // +UCGED: 2 // 4,,, - // ,,,,,,,,,,,... etc. + // ,,,,,,,,,,,,... 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 @@ -410,6 +469,8 @@ static int32_t getRadioParamsUcged2LaraR6(uAtClientHandle_t atHandle, uAtClientSkipParameters(atHandle, 4); // Read and convert it to dBm pRadioParameters->rssiDbm = rssiUtranToDbm(uAtClientReadInt(atHandle)); + // Read and convert it to dB + pRadioParameters->snrDb = ecnoLevToDb(uAtClientReadInt(atHandle)); // Ignore the rest break; case 4: @@ -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; @@ -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"); } @@ -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) { @@ -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; } } diff --git a/cell/src/u_cell_private.c b/cell/src/u_cell_private.c index 838e6990..bb635b52 100644 --- a/cell/src/u_cell_private.c +++ b/cell/src/u_cell_private.c @@ -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, diff --git a/cell/src/u_cell_private.h b/cell/src/u_cell_private.h index 5aa76161..2c005ea2 100644 --- a/cell/src/u_cell_private.h +++ b/cell/src/u_cell_private.h @@ -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