Cellular Interface - Handling AT+QMTRECV blank OK responses on BG96 modem

Hi,

I’ve written several methods for handling the AT+QMTRECV call on the BG96 module. This call retrieves incoming publish data from a series of buffers. When there is data in the buffer, the response is in the form of +QMTRECV: <client_idx>,,,[<payload_len>,] and my handling works correctly.

However, when there is no message in the buffer all I get back is OK with no prefix. This results in a number of errors being logged by the library. Is there a way I could modify my methods so that receiving just OK is still considered a success without any data? My code is below since I can’t post any links or files yet.

Code
static CellularATError_t getMqttDataFromResp( const CellularATCommandResponse_t * pAtResp,
                                          const _mqttDataRecv_t * pDataRecv,
                                          uint32_t outBufSize )
{
    CellularATError_t atCoreStatus = CELLULAR_AT_SUCCESS;
    const char * pInputLine = NULL;
    uint32_t dataLenToCopy = 0;

    /* Check if the received data size is greater than the output buffer size. */
    if( *pDataRecv->pDataLen > outBufSize )
    {
        LogError( ( "Data is turncated, received data length %d, out buffer size %d",
                    *pDataRecv->pDataLen, outBufSize ) );
        dataLenToCopy = outBufSize;
        *pDataRecv->pDataLen = outBufSize;
    }
    else
    {
        dataLenToCopy = *pDataRecv->pDataLen;
    }

    /* Data is stored in the next intermediate response. */
    if( pAtResp->pItm->pNext != NULL )
    {
        pInputLine = pAtResp->pItm->pNext->pLine + 1; // To skip the first quote mark

        if( ( pInputLine != NULL ) && ( dataLenToCopy > 0U ) )
        {
            /* Copy the data to the out buffer. */
            ( void ) memcpy( ( void * ) pDataRecv->pData, ( const void * ) pInputLine, dataLenToCopy );
        }
        else
        {
            LogError( ( "Receive Data: Data pointer NULL" ) );
            atCoreStatus = CELLULAR_AT_BAD_PARAMETER;
        }
    }
    else if( *pDataRecv->pDataLen == 0U )
    {
        /* Receive command success but no data. */
        LogDebug( ( "Receive Data: no data" ) );
    }
    else
    {
        LogError( ( "Receive Data: Intermediate response empty" ) );
        atCoreStatus = CELLULAR_AT_BAD_PARAMETER;
    }

    return atCoreStatus;
}

static CellularPktStatus_t _Cellular_RecvMqttData( CellularContext_t * pContext,
                                                   const CellularATCommandResponse_t * pAtResp,
                                                   void * pData,
                                                   uint16_t dataLen )
{
    CellularATError_t atCoreStatus = CELLULAR_AT_SUCCESS;
    CellularPktStatus_t pktStatus = CELLULAR_PKT_STATUS_OK;
    char * pInputLine = NULL, * pToken = NULL;
    const _mqttDataRecv_t * pDataRecv = ( _mqttDataRecv_t * ) pData;
    int32_t tempValue = 0;

    if( pContext == NULL )
    {
        LogError( ( "Receive Data: invalid context" ) );
        pktStatus = CELLULAR_PKT_STATUS_FAILURE;
    }
    else if( ( pAtResp == NULL ) || ( pAtResp->pItm == NULL ) || ( pAtResp->pItm->pLine == NULL ) )
    {
        LogError( ( "Receive Data: response is invalid" ) );
        pktStatus = CELLULAR_PKT_STATUS_FAILURE;
    }
    else if( ( pDataRecv == NULL ) || ( pDataRecv->pData == NULL ) || ( pDataRecv->pDataLen == NULL ) )
    {
        LogError( ( "Receive Data: Bad param" ) );
        pktStatus = CELLULAR_PKT_STATUS_BAD_PARAM;
    }
    else
    {
        uint16_t i = 0;
        pInputLine = pAtResp->pItm->pLine;

        // Loop through message tokens till we reach topic
        for (i = 0; i < 3; i ++)
        {
            if ( atCoreStatus == CELLULAR_AT_SUCCESS)
            {
                atCoreStatus = Cellular_ATGetNextTok ( &pInputLine, &pToken);
            }
            else
            {
                break;
            }
        }

        // Store topic and topic size in structure
        if ( atCoreStatus == CELLULAR_AT_SUCCESS)
        {
            *pDataRecv->pTopicLength = strlen(pToken);
            ( void ) memcpy( ( void * ) pDataRecv->pTopic, ( const void * ) pToken, *pDataRecv->pTopicLength );
            atCoreStatus = Cellular_ATGetNextTok ( &pInputLine, &pToken );
        }

        // Get the size of the received message
        if ( atCoreStatus == CELLULAR_AT_SUCCESS )
        {
            atCoreStatus = Cellular_ATStrtoi( pToken, 10, &tempValue );
            if( ( tempValue >= ( int32_t ) 0 ) && ( tempValue < ( ( int32_t ) CELLULAR_MQTT_MAX_RECV_DATA_LEN ) ) )
            {
                *pDataRecv->pDataLen = ( uint32_t ) tempValue;
            }
            else
            {
                LogError( ( "Error in Data Length Processing: No valid digit found. Token %s", pToken ) );
                atCoreStatus = CELLULAR_AT_ERROR;
            }
        }

        /* Process the data buffer. */
        if( atCoreStatus == CELLULAR_AT_SUCCESS )
        {
            atCoreStatus = getMqttDataFromResp( pAtResp, pDataRecv, dataLen );
        }

        pktStatus = _Cellular_TranslateAtCoreStatus( atCoreStatus );
    }

    return pktStatus;
}

static CellularPktStatus_t mqttRecvDataPrefix( void * pCallbackContext,
                                                 char * pLine,
                                                 uint32_t lineLength,
                                                 char ** ppDataStart,
                                                 uint32_t * pDataLength )
{
    char * pDataStart = NULL;
    int32_t tempValue = 0;
    CellularATError_t atResult = CELLULAR_AT_SUCCESS;
    CellularPktStatus_t pktStatus = CELLULAR_PKT_STATUS_OK;
    uint32_t i = 0;
    const char delimiter = ',';
    uint8_t delimiterCount = 0;

    ( void ) pCallbackContext;

    if( ( pLine == NULL ) || ( ppDataStart == NULL ) || ( pDataLength == NULL ) )
    {
        pktStatus = CELLULAR_PKT_STATUS_BAD_PARAM;
    }
    else
    {
        if ( strncmp( pLine, MQTT_DATA_PREFIX_STRING, MQTT_DATA_PREFIX_STRING_LENGTH ) == 0)
        {
            pDataStart = pLine;
            char dataSizeSubString[5] = "\0";
            uint8_t subIndex = 0;
            //Loop through line looking for delimiters
            for ( i = 0; i < lineLength; i++ )
            {
                // After third delimiter, payload size begins
                // so copy it to local string to find the data size
                if (delimiterCount == 3 && subIndex < 5)
                {
                    dataSizeSubString[subIndex] = pDataStart[i];
                    subIndex += 1;
                }
                else if (delimiterCount == 4) // Last comma means we've reached the payload part
                {                             // of the message.
                    dataSizeSubString[subIndex - 1] = '\0';
                    atResult = Cellular_ATStrtoi(dataSizeSubString, 10, &tempValue);
                    if ( atResult == CELLULAR_AT_SUCCESS )
                    {
                        if( ( tempValue >= 0 ) &&
                           ( tempValue < ( int32_t ) CELLULAR_MQTT_MAX_RECV_DATA_LEN ) )
                        {
                           *pDataLength = ( uint32_t ) tempValue + 2; //Taking into account the quotes
                           pDataStart[i-1] = '\0';
                           pDataStart = &pDataStart[i];
                           LogDebug( ( "Start of data found at 0x%08x with length %ld", *ppDataStart, tempValue ) )
                        }
                        else
                        {
                           *pDataLength = 0;
                           pDataStart = NULL;
                           LogError( ( "QMTRECV data larger than CELLULAR_MQTT_MAX_RECV_DATA_LEN: %ld", tempValue ) );
                        }
                    }
                    else
                    {
                        *pDataLength = 0;
                        pDataStart = NULL;
                        LogError( ( "Could not process data size in prefix sub string: %s", dataSizeSubString ) );
                    }
                    break;
                }

                if ( pDataStart[i] == delimiter)
                {
                    delimiterCount += 1;
                }
            }

            if ( i == lineLength)
            {
                LogError( ( "Could not find all the fields in QMTRECV" ) );
                *pDataLength = 0;
                pDataStart = NULL;
                pktStatus = CELLULAR_PKT_STATUS_SIZE_MISMATCH;
            }
        }

        *ppDataStart = pDataStart;
    }
    return pktStatus;
}

CellularError_t Cellular_MqttReadIncomingPublish( CellularHandle_t cellularHandle,
                                                  uint8_t mqttContextId,
                                                  uint8_t mqttBufferIndex,
                                                  uint8_t * pBuffer,
                                                  uint32_t bufferLength,
                                                  uint32_t * pReceivedDataLength,
                                                  char * topic,
                                                  uint32_t topicBufferLength,
                                                  uint32_t * receivedTopicLength)
{
    CellularContext_t * pContext = ( CellularContext_t * ) cellularHandle;
    CellularError_t cellularStatus = CELLULAR_SUCCESS;
    CellularPktStatus_t pktStatus = CELLULAR_PKT_STATUS_OK;
    char cmdBuf[ CELLULAR_AT_CMD_TYPICAL_MAX_SIZE ] = { '\0' };
    uint32_t recvTimeout = DATA_READ_TIMEOUT_MS;
    _mqttDataRecv_t dataRecv =
    {
        pReceivedDataLength,
        pBuffer,
        bufferLength,
        topic,
        receivedTopicLength,
        topicBufferLength
    };
    CellularAtReq_t atReqMqttRecv =
    {
        cmdBuf,
        CELLULAR_AT_MULTI_DATA_WO_PREFIX,
        "+QMTRECV",
        _Cellular_RecvMqttData,
        ( void * ) &dataRecv,
        bufferLength,
    };

    cellularStatus = _Cellular_CheckLibraryStatus( pContext );

    if( cellularStatus != CELLULAR_SUCCESS )
    {
        LogError( ( "_Cellular_MqttReadIncomingPublish failed." ) );
    }
    else if( ( pBuffer == NULL ) || ( pReceivedDataLength == NULL ) || ( bufferLength == 0U ) )
    {
        LogError( ( "_Cellular_MqttReadIncomingPublish: Bad input Param." ) );
        cellularStatus = CELLULAR_BAD_PARAMETER;
    }
    else
    {
        ( void ) snprintf( cmdBuf, CELLULAR_AT_CMD_TYPICAL_MAX_SIZE,
                           "%s%d,%d", "AT+QMTRECV=", mqttContextId, mqttBufferIndex );
        pktStatus = _Cellular_TimeoutAtcmdDataRecvRequestWithCallback( pContext, atReqMqttRecv, recvTimeout, mqttRecvDataPrefix, NULL );

        if( pktStatus != CELLULAR_PKT_STATUS_OK )
        {
            /* Reset data handling parameters. */
            LogError( ( "_Cellular_MqttReadIncomingPublish: Data Receive fail, pktStatus: %d", pktStatus ) );
            cellularStatus = _Cellular_TranslatePktStatus( pktStatus );
        }
    }

    return cellularStatus;
}

Hi @azeater,
In my first glance, your code should work correctly. While getting OK response, it should pass it into token table and mark it done there with success result. Is it possible to enable logs on Cellular interface and share the log with us?

Thank you.

Going off of @ActoryOu’s response - you can enable the different levels of logging by defining LogWarn, LogError, LogInfo, and LogDebug in cellular_config.h for your build.

Thanks for all of your quick responses and Merry Christmas! Here are some of the logs I captured when running the code with the following AT commands:

>> AT+QMTRECV=0,4
<< OK

Logs

1734999278855 [ERROR] [Cellular] [CellularLib] [_Cellular_RecvMqttData:4426] Receive Data: response is invalid

1734999278855 [WARN] [IotHandler] [CellularLib] [_Cellular_AtcmdRequestTimeoutWithCallbackRaw:246] Modem returns error in sending AT command AT+QMTRECV=0,4, pktStatus 2.

1734999278855 [ERROR] [IotHandler] [CellularLib] [Cellular_MqttReadIncomingPublish:4628] _Cellular_MqttReadIncomingPublish: Data Receive fail, pktStatus: 2

1734999278855 [ERROR] [IotHandler] [CellularLib] [_Cellular_TranslatePktStatus:487] _Cellular_TranslatePktStatus: Status 2

1734999278855 [ERROR] [IotHandler] [CellularLib] [CellularMqttWrapper_Receive:558] Could not read incoming MQTT message from buffer 4

Likely the failure return is from _Cellular_RecvMqttData(). When you received nothing but OK, pAtResp->pItm and pAtResp->pItm->pLine are both NULL. Thus, I’d suggest you to update that function in bellow way. When the status is true, that means it hits the success token table, which should be the case you’re wondering. Let me know if that work for you.

static CellularPktStatus_t _Cellular_RecvMqttData( CellularContext_t * pContext,
const CellularATCommandResponse_t * pAtResp,
void * pData,
uint16_t dataLen )
{
    /*...*/
-    else if( ( pAtResp == NULL ) || ( pAtResp->pItm == NULL ) || ( pAtResp->pItm->pLine == NULL ) )
-    {
-        LogError( ( "Receive Data: response is invalid" ) );
-        pktStatus = CELLULAR_PKT_STATUS_FAILURE;
-    }
+    else if( pAtResp == NULL )
+    {
+        LogError( ( "Receive Data: response is invalid" ) );
+        pktStatus = CELLULAR_PKT_STATUS_FAILURE;
+    }
+    else if( ( pAtResp->pItm == NULL ) || ( pAtResp->pItm->pLine == NULL ) )
+    {
+        if( pAtResp->status == true )
+        {
+            /* Received OK only, keep the return value as CELLULAR_PKT_STATUS_OK */
+        }
+        else
+        {
+            LogError( ( "Received ERROR." ) );
+            pktStatus = CELLULAR_PKT_STATUS_FAILURE;
+        }
+    }
    /*...*/
}

Thank you.

1 Like

Thanks for your help on this one Actory! That was it in the end.

Actually, I’ve discovered a bit of an edge case here and I’m wondering if you could help me further.

+QMTRECV is also a URC that I receive from the modem when there is a message in the MQTT buffer, in the form of +QMTRECV: [mqttId], [bufferId]. If I receive this URC directly after receiving OK mqttRecvDataPrefix still gets called as if this URC is part of the requested response when it is not.

Is there a way to indicate that once OK is received that the command response sequence is completed and everything received after that is URC?

Hi @azeater,
I have a suggestion like below. The concept is to store the result by the custom context in mqttRecvDataPrefix function, then treat the next URC as the mis-matching case. Below is the pseudo code of it.

static CellularPktStatus_t mqttRecvDataPrefix( void * pCallbackContext,
                                                 char * pLine,
                                                 uint32_t lineLength,
                                                 char ** ppDataStart,
                                                 uint32_t * pDataLength )
{
+    /* Use the custom context, which is initialized to false, to store the parsed result. */
+    bool * pParsed =  ( uint8_t * ) pCallbackContext;

    if( ( pLine == NULL ) || ( ppDataStart == NULL ) || ( pDataLength == NULL ) )
    {
        pktStatus = CELLULAR_PKT_STATUS_BAD_PARAM;
    }
+   else if( *pParsed == false )
+   {
+       /* This AT-command is done parsing, any following data is not matching anyway.  */
+       pktStatus = CELLULAR_PKT_STATUS_PREFIX_MISMATCH;
+   }
    else
    {
+       if( strncmp( pLine, "OK", strlen("OK") ) == 0 )
+       {
+           *pParsed = true;
+       }
+       else if ( strncmp( pLine, MQTT_DATA_PREFIX_STRING, MQTT_DATA_PREFIX_STRING_LENGTH ) == 0)
-       if ( strncmp( pLine, MQTT_DATA_PREFIX_STRING, MQTT_DATA_PREFIX_STRING_LENGTH ) == 0)
        {
            /* ... */
        }
    }
    return pktStatus;
}
1 Like

Thanks for the response! That seems to be working well now - cheers!

Thanks for reporting back! Glad to see that work for you!