diff --git a/cell/api/u_cell_net.h b/cell/api/u_cell_net.h index cb2ec7ff..4af45bfc 100644 --- a/cell/api/u_cell_net.h +++ b/cell/api/u_cell_net.h @@ -761,6 +761,17 @@ int32_t uCellNetSetBaseStationConnectionStatusCallback(uDeviceHandle_t cellHandl uCellNetStatus_t uCellNetGetNetworkStatus(uDeviceHandle_t cellHandle, uCellNetRegDomain_t domain); +/** Get the last EMM reject cause value sent by the network; not + * supported by all module types (for example SARA-R4 series + * modules do not support this). + * + * @param cellHandle the handle of the cellular instance. + * @return on success the last EMM cause from the network, + * see appendix A.3 of the AT commands manual, + * else negative error code. + */ +int32_t uCellNetGetLastEmmRejectCause(uDeviceHandle_t cellHandle); + /** Get a value indicating whether the module is registered on * the network, roaming or home networks. * diff --git a/cell/src/u_cell_net.c b/cell/src/u_cell_net.c index 5be00aac..36702cf6 100644 --- a/cell/src/u_cell_net.c +++ b/cell/src/u_cell_net.c @@ -1061,6 +1061,145 @@ static int32_t readNextScanItem(uCellPrivateInstance_t *pInstance, return errorCodeOrNumber; } +// Read the last EMM cause sent by the network. +static int32_t getEmmRejectCause(const uCellPrivateInstance_t *pInstance) +{ + int32_t errorCodeOrEmmCause = (int32_t) U_ERROR_COMMON_NOT_SUPPORTED; + uAtClientHandle_t atHandle = pInstance->atHandle; + int32_t x = -1; + char buffer[32]; // Enough room for "EMM cause" + + if (!U_CELL_PRIVATE_MODULE_IS_SARA_R4(pInstance->pModule->moduleType)) { + uAtClientLock(atHandle); + uAtClientCommandStart(atHandle, "AT+CEER"); + uAtClientCommandStop(atHandle); + // Should get back +CEER: [,,] + uAtClientResponseStart(atHandle, "+CEER:"); + // Read + uAtClientReadString(atHandle, buffer, sizeof(buffer), false); + // Read + x = uAtClientReadInt(atHandle); + // Skip + uAtClientSkipParameters(atHandle, 1); + uAtClientResponseStop(atHandle); + errorCodeOrEmmCause = uAtClientUnlock(atHandle); + if ((errorCodeOrEmmCause == 0) && (x >= 0) && + (strncmp(buffer, "EMM cause", sizeof(buffer)) == 0)) { + errorCodeOrEmmCause = x; + } + } + + return errorCodeOrEmmCause; +} + +// Handle the response to the CxREG command, where regType +// is an index into gRegTypes[]. +static void handleCxRegResponse(uCellPrivateInstance_t *pInstance, int32_t regType) +{ + uCellNetStatus_t status = U_CELL_NET_STATUS_UNKNOWN; + uAtClientHandle_t atHandle = pInstance->atHandle; + int32_t firstInt; + int32_t status3gpp; + int32_t skippedParameters = 1; + int32_t rat3gpp = -1; + bool gotUrc = false; + char buffer[U_CELL_PRIVATE_CELL_ID_LOGICAL_SIZE + 1]; // +1 for terminator + + uAtClientResponseStart(atHandle, gRegTypes[regType].pResponseStr); + + // It is possible for the module to spit-out + // a "+CxREG: y" URC while we're waiting for + // the "+CxREG: x,y" response from the AT+CxREG + // command. So the first integer might either by the mode + // we set, , being sent back to us or it might be the + // value of the URC. The dodge to distinguish the + // two is based on the fact that our values for match status + // values that mean "not registered", so we can do this: + // (a) if the first integer matches the /mode + // parameter from the AT+CxREG=,... command, then either + // i) this is the response we were expecting and + // the status etc. parameters follow, or, + // ii) this is a URC with a value indicating we are not + // registered and hence will not be followed + // by any further parameters, + // (b) if the first integer does not match then this + // is a URC and the first integer is the value. + + // ...except if this is LENA-R8 which, just to be different, + // and only for the +CREG command, omits the for both the + // information response and the URC cases. + if ((regType != 0 /* not CREG */) || + (pInstance->pModule->moduleType != U_CELL_MODULE_TYPE_LENA_R8)) { + firstInt = uAtClientReadInt(atHandle); + status3gpp = uAtClientReadInt(atHandle); + if ((firstInt == U_CELL_NET_CREG_OR_CGREG_TYPE) || + (firstInt == U_CELL_NET_CEREG_TYPE)) { + // case (a.i) or (a.ii) + if (status3gpp < 0) { + // case (a.ii) + gotUrc = true; + status3gpp = firstInt; + uAtClientClearError(atHandle); + } + } else { + // case (b), it's the URC + gotUrc = true; + status3gpp = firstInt; + } + } else { + // LENA-R8 +CREG information response + status3gpp = uAtClientReadInt(atHandle); + } + if (gotUrc) { + // Read the actual response, which should follow + uAtClientResponseStart(atHandle, + gRegTypes[regType].pResponseStr); + uAtClientReadInt(atHandle); + status3gpp = uAtClientReadInt(atHandle); + } + if ((status3gpp >= 0) && + (status3gpp < (int32_t) (sizeof(g3gppStatusToCellStatus) / + sizeof(g3gppStatusToCellStatus[0])))) { + status = g3gppStatusToCellStatus[status3gpp]; + } + if (U_CELL_NET_STATUS_MEANS_REGISTERED(status)) { + // Skip / + if ((regType == 2 /* CEREG */) && (gRegTypes[regType].type == 4) && + (((pInstance->pModule->moduleType == U_CELL_MODULE_TYPE_SARA_R410M_02B) || + (pInstance->pModule->moduleType == U_CELL_MODULE_TYPE_SARA_R412M_02B)) || + ((pInstance->pModule->moduleType == U_CELL_MODULE_TYPE_LARA_R6) && + !gotUrc))) { + // SARA-R41x-02B modules, and LARA-R6 modules but only in the + // non-URC case, sneak an extra parameter in between + // and when U_CELL_NET_CEREG_TYPE is 4 so we need to + // skip an additional parameter + skippedParameters++; + } + uAtClientSkipParameters(atHandle, skippedParameters); + // Read CI, which is hex, encoded as an 8-digit string + if (uAtClientReadString(atHandle, buffer, sizeof(buffer), false) > 0) { + pInstance->radioParameters.cellIdLogical = strtol(buffer, NULL, 16); + } + // Read the RAT that we're on + rat3gpp = uAtClientReadInt(atHandle); + if (rat3gpp < 0) { + if (regType == 2 /* CEREG */) { + // LARA-R6 sometime misses out the RAT in the +CEREG + // response; we need something... + rat3gpp = 7; // LTE + } else if (regType == 1 /* CGREG */) { + // LENA-R8 frequently misses out the RAT in the +CGREG + // response; we need something... + rat3gpp = 3; // GSM/GPRS/EDGE + } + } + } + // Set the status + setNetworkStatus(pInstance, status, rat3gpp, regType, false); + + uAtClientResponseStop(atHandle); +} + // Register with the cellular network static int32_t registerNetwork(uCellPrivateInstance_t *pInstance, const char *pMccMnc) @@ -1070,14 +1209,7 @@ static int32_t registerNetwork(uCellPrivateInstance_t *pInstance, bool keepGoing = true; bool deviceErrorDetected = false; int32_t regType; - int32_t firstInt; - int32_t status3gpp; - uCellNetStatus_t status; - int32_t skippedParameters = 1; - int32_t rat3gpp = -1; - bool gotUrc; size_t errorCount = 0; - char buffer[U_CELL_PRIVATE_CELL_ID_LOGICAL_SIZE + 1]; // +1 for terminator // Come out of airplane mode and try to register // Wait for flip time to expire first though @@ -1156,104 +1288,13 @@ static int32_t registerNetwork(uCellPrivateInstance_t *pInstance, // one at a time. if (gRegTypes[regType].supportedRatsBitmap & pInstance->pModule->supportedRatsBitmap) { - status = U_CELL_NET_STATUS_UNKNOWN; uAtClientLock(atHandle); uAtClientTimeoutSet(atHandle, pInstance->pModule->responseMaxWaitMs); uAtClientCommandStart(atHandle, gRegTypes[regType].pQueryStr); uAtClientCommandStop(atHandle); - uAtClientResponseStart(atHandle, gRegTypes[regType].pResponseStr); - // It is possible for the module to spit-out - // a "+CxREG: y" URC while we're waiting for - // the "+CxREG: x,y" response from the AT+CxREG - // command. So the first integer might either by the mode - // we set, , being sent back to us or it might be the - // value of the URC. The dodge to distinguish the - // two is based on the fact that our values for match status - // values that mean "not registered", so we can do this: - // (a) if the first integer matches the /mode - // parameter from the AT+CxREG=,... command, then either - // i) this is the response we were expecting and - // the status etc. parameters follow, or, - // ii) this is a URC with a value indicating we are not - // registered and hence will not be followed - // by any further parameters, - // (b) if the first integer does not match then this - // is a URC and the first integer is the value. - - // ...except if this is LENA-R8 which, just to be different, - // and only for the +CREG command, omits the for both the - // information response and the URC cases. - gotUrc = false; - if ((regType != 0 /* not CREG */) || - (pInstance->pModule->moduleType != U_CELL_MODULE_TYPE_LENA_R8)) { - firstInt = uAtClientReadInt(atHandle); - status3gpp = uAtClientReadInt(atHandle); - if ((firstInt == U_CELL_NET_CREG_OR_CGREG_TYPE) || - (firstInt == U_CELL_NET_CEREG_TYPE)) { - // case (a.i) or (a.ii) - if (status3gpp < 0) { - // case (a.ii) - gotUrc = true; - status3gpp = firstInt; - uAtClientClearError(atHandle); - } - } else { - // case (b), it's the URC - gotUrc = true; - status3gpp = firstInt; - } - } else { - // LENA-R8 +CREG information response - status3gpp = uAtClientReadInt(atHandle); - } - if (gotUrc) { - // Read the actual response, which should follow - uAtClientResponseStart(atHandle, - gRegTypes[regType].pResponseStr); - uAtClientReadInt(atHandle); - status3gpp = uAtClientReadInt(atHandle); - } - if ((status3gpp >= 0) && - (status3gpp < (int32_t) (sizeof(g3gppStatusToCellStatus) / - sizeof(g3gppStatusToCellStatus[0])))) { - status = g3gppStatusToCellStatus[status3gpp]; - } - if (U_CELL_NET_STATUS_MEANS_REGISTERED(status)) { - // Skip / - if ((regType == 2 /* CEREG */) && (gRegTypes[regType].type == 4) && - (((pInstance->pModule->moduleType == U_CELL_MODULE_TYPE_SARA_R410M_02B) || - (pInstance->pModule->moduleType == U_CELL_MODULE_TYPE_SARA_R412M_02B)) || - ((pInstance->pModule->moduleType == U_CELL_MODULE_TYPE_LARA_R6) && - !gotUrc))) { - // SARA-R41x-02B modules, and LARA-R6 modules but only in the - // non-URC case, sneak an extra parameter in between - // and when U_CELL_NET_CEREG_TYPE is 4 so we need to - // skip an additional parameter - skippedParameters++; - } - uAtClientSkipParameters(atHandle, skippedParameters); - // Read CI, which is hex, encoded as an 8-digit string - if (uAtClientReadString(atHandle, buffer, sizeof(buffer), false) > 0) { - pInstance->radioParameters.cellIdLogical = strtol(buffer, NULL, 16); - } - // Read the RAT that we're on - rat3gpp = uAtClientReadInt(atHandle); - if (rat3gpp < 0) { - if (regType == 2 /* CEREG */) { - // LARA-R6 sometime misses out the RAT in the +CEREG - // response; we need something... - rat3gpp = 7; // LTE - } else if (regType == 1 /* CGREG */) { - // LENA-R8 frequently misses out the RAT in the +CGREG - // response; we need something... - rat3gpp = 3; // GSM/GPRS/EDGE - } - } - } - // Set the status - setNetworkStatus(pInstance, status, rat3gpp, regType, false); - uAtClientResponseStop(atHandle); + // Handle the response to the CxREG command + handleCxRegResponse(pInstance, regType); if (uAtClientUnlock(atHandle) != 0) { // We're prodding the module pretty often // while it is busy, it is possible for @@ -2452,6 +2493,9 @@ int32_t uCellNetConnect(uDeviceHandle_t cellHandle, } while ((errorCode != 0) && (pApnConfig != NULL) && (*pApnConfig != '\0') && keepGoingLocalCb(pInstance)); + // Populate, or reset, the last EMM cause + pInstance->lastEmmRejectCause = getEmmRejectCause(pInstance); + if (errorCode == 0) { // Remember the MCC/MNC in case we need to deactivate // and reactivate context later and that causes @@ -2477,7 +2521,6 @@ int32_t uCellNetConnect(uDeviceHandle_t cellHandle, // Take away the callback again pInstance->pKeepGoingCallback = NULL; pInstance->startTimeMs = 0; - } } else { uPortLog("U_CELL_NET: already connected.\n"); @@ -2542,6 +2585,9 @@ int32_t uCellNetRegister(uDeviceHandle_t cellHandle, errorCode = waitAttach(pInstance); } + // Populate, or reset, the last EMM cause + pInstance->lastEmmRejectCause = getEmmRejectCause(pInstance); + if (errorCode == 0) { // Remember the MCC/MNC in case we need to deactivate // and reactivate context later and that causes @@ -2565,7 +2611,6 @@ int32_t uCellNetRegister(uDeviceHandle_t cellHandle, // Take away the callback again pInstance->pKeepGoingCallback = NULL; pInstance->startTimeMs = 0; - } } @@ -2690,6 +2735,8 @@ int32_t uCellNetActivate(uDeviceHandle_t cellHandle, // be registered but not attached. errorCode = waitAttach(pInstance); } + // Update again, in case of failure + pInstance->lastEmmRejectCause = getEmmRejectCause(pInstance); if (errorCode != 0) { // Switch radio off after failure radioOff(pInstance); @@ -2781,6 +2828,8 @@ int32_t uCellNetDeactivate(uDeviceHandle_t cellHandle, if (errorCode != 0) { uPortLog("U_CELL_NET: unable to deactivate context.\n"); } + // Populate, or reset, the last EMM cause + pInstance->lastEmmRejectCause = getEmmRejectCause(pInstance); } } @@ -2833,6 +2882,8 @@ int32_t uCellNetDisconnect(uDeviceHandle_t cellHandle, } else { uPortLog("U_CELL_NET: unable to disconnect.\n"); } + // Populate, or reset, the last EMM cause + pInstance->lastEmmRejectCause = getEmmRejectCause(pInstance); } U_PORT_MUTEX_UNLOCK(gUCellPrivateMutex); @@ -3254,6 +3305,35 @@ uCellNetStatus_t uCellNetGetNetworkStatus(uDeviceHandle_t cellHandle, return (uCellNetStatus_t) errorCodeOrStatus; } +// Get the last EMM cause sent by the network. +int32_t uCellNetGetLastEmmRejectCause(uDeviceHandle_t cellHandle) +{ + int32_t errorCodeOrEmmCause = (int32_t) U_ERROR_COMMON_NOT_INITIALISED; + uCellPrivateInstance_t *pInstance; + + if (gUCellPrivateMutex != NULL) { + + U_PORT_MUTEX_LOCK(gUCellPrivateMutex); + + pInstance = pUCellPrivateGetInstance(cellHandle); + errorCodeOrEmmCause = (int32_t) U_ERROR_COMMON_INVALID_PARAMETER; + if (pInstance != NULL) { + // Retrieve the stored value, which may hold a + // reject cause from a registration atttempt + errorCodeOrEmmCause = pInstance->lastEmmRejectCause; + if (errorCodeOrEmmCause == 0) { + // If there wasn't a stored value, check again + // in case something has happened since + errorCodeOrEmmCause = getEmmRejectCause(pInstance); + } + } + + U_PORT_MUTEX_UNLOCK(gUCellPrivateMutex); + } + + return errorCodeOrEmmCause; +} + // Get a value whether the module is registered on the network. bool uCellNetIsRegistered(uDeviceHandle_t cellHandle) { diff --git a/cell/src/u_cell_private.h b/cell/src/u_cell_private.h index 22c2cf29..82154930 100644 --- a/cell/src/u_cell_private.h +++ b/cell/src/u_cell_private.h @@ -446,6 +446,7 @@ typedef struct uCellPrivateInstance_t { networkStatus[U_CELL_PRIVATE_NET_REG_TYPE_MAX_NUM]; /**< Registation status for each type, separating CREG, CGREG and CEREG. */ uCellNetRat_t rat[U_CELL_PRIVATE_NET_REG_TYPE_MAX_NUM]; /**< The active RAT for each registration type. */ + int32_t lastEmmRejectCause; /**< Used by uCellNetGetLastEmmRejectCause() only. */ uCellPrivateRadioParameters_t radioParameters; /**< The radio parameters. */ int32_t startTimeMs; /**< Used while connecting and scanning. */ int32_t connectedAtMs; /**< When a connection was last established, diff --git a/cell/test/u_cell_net_test.c b/cell/test/u_cell_net_test.c index 019bda52..7141f835 100644 --- a/cell/test/u_cell_net_test.c +++ b/cell/test/u_cell_net_test.c @@ -206,6 +206,7 @@ U_PORT_TEST_FUNCTION("[cellNet]", "cellNetConnectDisconnectPlus") char parameter1[5]; // enough room for "Boo!" char parameter2[5]; // enough room for "Bah!" int32_t resourceCount; + int32_t networkCause; strncpy(parameter1, "Boo!", sizeof(parameter1)); strncpy(parameter2, "Bah!", sizeof(parameter2)); @@ -272,6 +273,12 @@ U_PORT_TEST_FUNCTION("[cellNet]", "cellNetConnectDisconnectPlus") U_PORT_TEST_ASSERT(x < 0); } + // Get the network cause + networkCause = uCellNetGetLastEmmRejectCause(cellHandle); + U_TEST_PRINT_LINE("network cause is %d.", networkCause); + U_PORT_TEST_ASSERT((networkCause == (int32_t) U_ERROR_COMMON_NOT_SUPPORTED) || + (networkCause == 0)); + // Connect with a very short time-out to show that aborts work U_TEST_PRINT_LINE("testing abort of connection attempt due to timeout."); gStopTimeMs = uPortGetTickTimeMs() + 1000; @@ -345,6 +352,12 @@ U_PORT_TEST_FUNCTION("[cellNet]", "cellNetConnectDisconnectPlus") U_PORT_TEST_ASSERT(!gHasBeenConnected); } + // Get the network cause + networkCause = uCellNetGetLastEmmRejectCause(cellHandle); + U_TEST_PRINT_LINE("network cause is now %d.", networkCause); + U_PORT_TEST_ASSERT((networkCause == (int32_t) U_ERROR_COMMON_NOT_SUPPORTED) || + (networkCause == 0)); + // Check that we have an active RAT // Note: can't check that it's the right one for this module // as we only keep the configurable RATs which are a subset of the @@ -464,6 +477,12 @@ U_PORT_TEST_FUNCTION("[cellNet]", "cellNetConnectDisconnectPlus") // will have deactivated what we had and been unable to // activate the new one U_PORT_TEST_ASSERT(uCellNetGetIpAddressStr(cellHandle, buffer) < 0); + + // Get the network cause + networkCause = uCellNetGetLastEmmRejectCause(cellHandle); + U_TEST_PRINT_LINE("network cause with incorrect APN is %d.", networkCause); + U_PORT_TEST_ASSERT((networkCause == (int32_t) U_ERROR_COMMON_NOT_SUPPORTED) || + (networkCause > 0)); } #endif @@ -510,6 +529,7 @@ U_PORT_TEST_FUNCTION("[cellNet]", "cellNetScanRegActDeact") int32_t y = 0; uCellNetRat_t rat = U_CELL_NET_RAT_UNKNOWN_OR_NOT_USED; int32_t resourceCount; + int32_t networkCause; // In case a previous test failed uCellTestPrivateCleanup(&gHandles); @@ -629,6 +649,12 @@ U_PORT_TEST_FUNCTION("[cellNet]", "cellNetScanRegActDeact") U_PORT_TEST_ASSERT(y > 0); U_PORT_TEST_ASSERT(strlen(buffer) == y); + // Get the network cause + networkCause = uCellNetGetLastEmmRejectCause(cellHandle); + U_TEST_PRINT_LINE("network cause is %d.", networkCause); + U_PORT_TEST_ASSERT((networkCause == (int32_t) U_ERROR_COMMON_NOT_SUPPORTED) || + (networkCause == 0)); + // Deactivate the context rat = uCellNetGetActiveRat(cellHandle); U_TEST_PRINT_LINE("deactivating context..."); @@ -734,6 +760,12 @@ U_PORT_TEST_FUNCTION("[cellNet]", "cellNetScanRegActDeact") U_PORT_TEST_ASSERT(y < 0); // Get the IP address. U_PORT_TEST_ASSERT(uCellNetGetIpAddressStr(cellHandle, buffer) < 0); + + // Get the network cause + networkCause = uCellNetGetLastEmmRejectCause(cellHandle); + U_TEST_PRINT_LINE("network cause with incorrect APN is %d.", networkCause); + U_PORT_TEST_ASSERT((networkCause == (int32_t) U_ERROR_COMMON_NOT_SUPPORTED) || + (networkCause > 0)); } #endif @@ -788,6 +820,12 @@ U_PORT_TEST_FUNCTION("[cellNet]", "cellNetScanRegActDeact") U_PORT_TEST_ASSERT(y > 0); U_PORT_TEST_ASSERT(strlen(buffer) == y); + // Get the network cause + networkCause = uCellNetGetLastEmmRejectCause(cellHandle); + U_TEST_PRINT_LINE("network cause is now %d.", networkCause); + U_PORT_TEST_ASSERT((networkCause == (int32_t) U_ERROR_COMMON_NOT_SUPPORTED) || + (networkCause == 0)); + // Disconnect U_PORT_TEST_ASSERT(uCellNetDisconnect(cellHandle, NULL) == 0); @@ -842,6 +880,12 @@ U_PORT_TEST_FUNCTION("[cellNet]", "cellNetScanRegActDeact") U_PORT_TEST_ASSERT(y > 0); U_PORT_TEST_ASSERT(strlen(buffer) == y); + // Get the network cause + networkCause = uCellNetGetLastEmmRejectCause(cellHandle); + U_TEST_PRINT_LINE("network cause is finally %d.", networkCause); + U_PORT_TEST_ASSERT((networkCause == (int32_t) U_ERROR_COMMON_NOT_SUPPORTED) || + (networkCause == 0)); + // Disconnect U_TEST_PRINT_LINE("disconnecting..."); U_PORT_TEST_ASSERT(uCellNetDisconnect(cellHandle, NULL) == 0);