This thread has been locked.

If you have a related question, please click the "Ask a related question" button in the top right corner. The newly created question will be automatically linked to this question.

CC2652RB: How to use blePending?

Part Number: CC2652RB
Other Parts Discussed in Thread: CC2640, CC2650

We have a project based on the SimplePeripheral example.  We have a few oft-changing values we want to be able to read over BLE, such as the device time from the CC2652's RTC.  Rather than constantly updating a buffer to make sure these values are up-to-date when read, we instead want to be able fetch the data on demand when read.  I have guessed that the proper way to do this is by returning blePending in SimplePeripheral_ReadAttrCB(), as such:

    // See if request is regarding the Clock Characteristic Value
    if ( ! memcmp(pAttr->type.uuid, MyService_ClockUUID, pAttr->type.len) )
    {
        if ( offset > MYSERVICE_CLOCK_LEN ) // Prevent malicious ATT ReadBlob offsets.
        {
            status = ATT_ERR_INVALID_OFFSET;
        }
        else
        {
            if ( pAppCBs && pAppCBs->pfnReadCb )
                pAppCBs->pfnReadCb(connHandle, MYSERVICE_CLOCK_ID); // Call app function from stack task context.
            return blePending;
        }
    }

where I've created pAppCBs->pfnReadCb() the same way the application callbacks are done for SimplePeripheral_WriteAttrCB().  This enqueues a message for our application task and is processed by SimplePeripheral_processAppMsg(), which calls the following function:

static void MyService_processValueReadEvt(MyServiceReadCharacteristic_t *pData)
{
    switch(pData->paramID)
    {
        case MYSERVICE_CLOCK_ID:
        {
            bStatus_t status;
            attReadRsp_t clockRsp;
            uint16_t len;

            clockRsp.pValue = (uint8 *)GATT_bm_alloc( pData->connHandle, ATT_READ_RSP, MYSERVICE_CLOCK_LEN, &len );
            if ( clockRsp.pValue != NULL )
            {
                if (len != MYSERVICE_CLOCK_LEN)
                {
                    GATT_bm_free( (gattMsg_t *)&clockRsp, ATT_READ_RSP );
                    // TODO: couldn't allocate enough memory, but we still have to tell the BLE stack something to end "blePending", right?
                }
                else
                {
                    uint32_t time = Seconds_get();
                    memcpy(clockRsp.pValue, &time, len);
                    clockRsp.len = len;
                    status = ATT_ReadRsp(pData->connHandle, &clockRsp);
                    if ( status != SUCCESS )
                    {
                        GATT_bm_free( (gattMsg_t *)&clockRsp, ATT_READ_RSP );
                        // TODO: we failed to return the data for some reason; does the BLE stack still need us to do anything?
                    }
                }
            }
            else
            {
                // TODO: couldn't allocate any memory, but we still have to tell the BLE stack something, right?
            }
        }
        break;

My first question is: is this the right way to do this?  I had to do some digging to find attReadRsp_t and ATT_ReadRsp().  I have tested this and it works, though, so I know I'm at least on the right track.

My second question is: assuming the above is correct, what do I need to be calling in place of the comments in the code?  If I can't allocate the memory I need to send the Read Response back to the BLE stack, I'm assuming I still need to do something so it's not just blePending forever, right?  And if ATT_ReadRsp() isn't successful, do we still need to do something to keep the stack from waiting for a success?

  • Hello,

    I have assigned an expert to help you with your query. In the meantime, can you tell us what version of the SDK are you using? 

    Regards,

    Jan

  • Any update?

    I have a third, follow-up question for this expert: How much memory can be dynamically allocated for this, and can we increase that somewhere if need be?  We're also reading logs from the device; our page size with some overhead means a 288-byte characteristic.  That works fine as a static RAM buffer in the application, but what heap is used for the GATT_bm_alloc() call, and should we expect to be able to ask for 288 bytes?

  • Fourth question: How quickly do you need to respond with the data after pending?  Should we expect to have time to read the above from an external flash chip over SPI and encrypt it before returning it?

  • Hi Chris,

    I truly apologize for the delay. Can you verify if the following E2E threads answer your questions?

    https://e2e.ti.com/support/wireless-connectivity/bluetooth/f/bluetooth-forum/445751/blepending---cc2640

    https://e2e.ti.com/support/wireless-connectivity/bluetooth/f/bluetooth-forum/953408/cc2642r-delayed-response-using-gattservapp_readrsp/3543573

    I believe the way you are attempting to use blePending is correct.

    Regards,

    Jan

  • The second thread seems super relevant, and I'm surprised I didn't find it when I searched.  I'm not sure it fully answers my question, though, and it actually raises another one.  The main "answer" to the first thread, however, is a reference to an example that is supposedly found in a SimpleNP_GATT.c file, which the CC26x2 SDK does not seem to contain, so that's less helpful.  Let me go through everything I've asked in order to keep it straight...

    Question 1: Seems like a "yes."  I'm happy to see the example code in the second thread is basically exactly what I wrote on my own.

    Question 2: Still not clear what to do when I can't allocate the memory.  The example code in the second thread returns a eBTStatusNoMem that isn't defined anywhere in the SDK, and there's no context to say what it's even "returning" it to and how that calling function processes an error return.  So I still don't know what to do with an error, or how to tell the BLE stack to stop pending because there was an error, or if I even need to.

    Question 3: Still don't know what heap this is coming from, how much I can reliably ask for, and whether it can be increased somehow if need be.

    Question 4: The first thread may answer this, but it's 6 years old and for a different chip with a different SDK.  JXS said back then of the CC2640 "you need to make the Read Rsp call within 30 secs after initially receiving the Read Request from the client."  That would be plenty of time for us if that's still the case for the newer CC26x2 SDKs.  Is that still the case?

    Question 5: The example code in the second thread also seems to imply that there are three different ATT_READ_x_REQ types that all need to be handled differently, whereas I'm currently assuming it's always ATT_READ_REQ because that was the only one I knew about.  For our 4-byte time characteristic, that ends up being a safe accident.  But we have a 30-byte characteristic that has probably only worked by virtue of always negotiating a higher MTU.  I'm guessing that wouldn't work so hot on a pre-BLE4.2 phone that can't do Data Length Extensions.  And I certainly would have needed ATT_ReadBlobRsp() for the 288-byte characteristic.  So, what happens if you reply with the wrong ReadRsp vs ReadBlobRsp for the REQ type?  I guess I'll have to add the method as a param to the callback and include that in the message to our application task so I can respond the right way?  The example code doesn't mention offsets for the blob read case; do I not have to worry about that then?  Can I just respond with the whole 288 bytes and the stack will then chop it up as necessary?  I have my doubts, since I thought SimplePeripheral_ReadAttrCB() would get called and is going to then trigger my application callback again on every packet.  And then I'm not even sure why there's a case for ATT_READ_BY_TYPE_REQ.  I thought that was for service discovery; an individual characteristic attribute handle shouldn't receive that type of read request, right?  Can someone clarify all this as well?

  • Hello Chris,

    For Question 2, that chunk of code is pseudo code so it may not 100% correct. A better return status to use may be bleMemAllocError. The following thread describes how the GATT_bm_alloc() function works.

    https://e2e.ti.com/support/wireless-connectivity/bluetooth/f/bluetooth-forum/947014/launchxl-cc26x2r1-gatt_bm_alloc-returns-value

    For Question 3, the GATT_bm_alloc() allocation is handled by the OSAL layer. I believe you should be able to monitor the exact effect of the allocation on the heap by using ROV and the HEAPMGR_METRICS definition. The following E2E thread describes a good way to debug memory issues:

    https://e2e.ti.com/support/wireless-connectivity/bluetooth/f/bluetooth-forum/973731/cc2642r-debugging-memory-issues/3598030

    For Question 4, I believe this is still the case, but the best way to verify would be to test it.

    For Question 5, I believe your proposed solution would work, but as is the case with Question 4 the best way to verify would be to test.

    Regards,

    Jan

  • Q2: Yeah, I saw it was pseudo code, so I'm hoping the expert you mentioned can give a definitive answer.  I know how GATT_bm_alloc() works (or at least I think I did; that thread asks whether it can return a non-null pointer to fewer bytes than asked for, and I'm not sure the answer actually clearly says yes or no), but that's not what I'm asking about.  Let's say it failed to allocate memory on the heap and I got back a NULL; what do I do with that?  I'm in an message queue processing function in my application task, not a BLE stack callback; there's no radio-related calling function to return any result to.  If I do have the data, I call ATT_ReadRsp(); what do I call if I can't send data with ATT_ReadRsp()?  Surely I don't just leave the BLE stack hanging for 30 seconds if I know I'm never going to respond, right?

    Q3: So you're saying this does use the heap from the main CPU?  Not the RF Core's SRAM?

    Q4: I mean... I could try to verify it experimentally, but I don't know what to look for as an indication that the time period has expired.  if there's a timeout in the stack code someone could just tell me the value for, that would be nice.

    Q5: Maybe my fault for asking more than one question there, but there's still a lot of unanswered stuff in that paragraph that I can't just test and could use a definitive response on.

  • Hi Chris,

    I apologize for the delay. I reached out to a colleague for some insight and he provided me with some useful information. With regards to your latest questions.

    Q2 & Q5

    According to the Bluetooth specification, "If a server does not have sufficient resources to process a request, then the server shall respond with the ATT_ERROR_RSP PDU with the error code Insufficient Resources, with the Attribute Handle In Error set to 0x0000"  [BLUETOOTH CORE SPECIFICATION Version 5.2 | Vol 3, Part F, §3.3]. This section also covers what happens if you use an unsupported PDU.

    Q3

    This is correct. The memory is allocated on the heap and not on the RF core RAM.

    Q4

    According to the Bluetooth specification, "Once a client sends a request to a server, that client shall send no other request to the same server on the same ATT bearer until a response PDU has been received." [BLUETOOTH CORE SPECIFICATION Version 5.2 | Vol 3, Part F, §3.3.2]. This means that there is no timeout.

    If you have any additional questions or if these answers did not completely clarify your questions, then please let me know and we will try our best to get to the bottom of this as soon as possible.

    Sincerely,

    Jan

  • Q2: That's how the radio is supposed to respond, yes.  You still haven't told me what function I have to call to make the stack do that, though.  The stack is waiting for me to reply to it; that's the whole point of blePending.  If I have the data, I call ATT_ReadRsp().  My original question was and still is: If I don't have the data for some reason, like not being able to allocate the memory, what function do I call instead of ATT_ReadRsp()?

    Q4: All the more reason I need to know how to tell the stack the response is never coming if I can't allocate the memory, since it's not going to time out.  So the 30 seconds in the other thread you linked me is wrong?

    Q5: I'll break out all the unanswered questions from last week:

    Q5.1: So, what happens if you reply with the wrong ReadRsp vs ReadBlobRsp for the REQ type?

    Q5.2: Do I have to worry about offsets when doing ATT_ReadBlobRsp()?  Can I respond with a whole 288-byte characteristic and the stack will then chop it up as necessary?  I have my doubts, since I thought SimplePeripheral_ReadAttrCB() would get called and is going to then trigger my application callback again on every packet.

    Q5.3: Do I actually need a case for ATT_READ_BY_TYPE_REQ?  I thought that was for service discovery; an individual characteristic attribute handle shouldn't receive that type of read request, right?

  • Hi Chris,

    We believe the feature you should use is the ATT Delayed Request Feature. Here is some documentation describing this feature, how to use it, and its general behavior: https://dev.ti.com/tirex/content/simplelink_cc13x2_26x2_sdk_4_40_04_04/docs/ble5stack/ble_user_guide/html/ble-stack-5.x/gatt.html#delaying-an-att-read-request

    The above document also includes the code mentioned in the second E2E thread I linked above, but with additional explanations on how enable the feature and properly make use of it.

    Specifically with regards to Q5.1 : Answering with the wrong Rsp may lead to problems on the client side of the connection.

    Let me know if the link provided clarifies any of your questions. I will look into your queries in the meantime and get back to you as soon as possible.

    Regards,

    Jan

  • How is that any different from the aforementioned similar E2E thread?  All my same questions still apply.

    I'm not sure how much more clearly I can repeat myself.  You can't run pseudocode.  The example has no context.  It uses a return statement to send the bleMemAllocError somewhere.  What's the calling function, and how does it solve my problem?  The missing piece in all of this is still telling the stack that no response is coming.  I pasted my code in here over two weeks ago; you can clearly see that I can't just stick a return statement in my code and expect that exiting a task event in the main application is going to send anything to the BLE stack to keep it from sitting there indefinitely waiting on blePending.

  • Hi Chris,

    We are looking into this and will post an update by Monday (4/12). I apologize for any inconvenience this may cause.

    Regards,

    Jan

  • Hi Chris,

    I have consulted the team and after doing some research we have come to the following conclusions.

    Q1: Your implementation is correct. However, make sure you have added the ATT_DELAYED_REQ into the predefined configuration. This will ensure that our BLEStack will allow that kind of request to come in.

    Q2: If you do not have enough heap available, then you must implement a way to do a dummy reply. If this is not done, then after 30 seconds the ATT bearer will be disabled for the connection. This occurs because the device that sends a read request expects a read/error response back. This is discussed in the Bluetooth specification [BLUETOOTH CORE SPECIFICATION Version 5.1 | Vol 3, Part F, §3.3.3]

    Q3: As mentioned before, the memory is allocated on the on the heap and not on the RF Core. As long as you have enough heap, you should be able to allocate whatever you need. I recommend enabling heap metrics during debugging (https://dev.ti.com/tirex/content/simplelink_cc13x2_26x2_sdk_4_40_04_04/docs/ble5stack/ble_user_guide/html/ble-stack-5.x-guide/debugging-index.html#debugging-common-heap-issues). This will give you a good idea of exactly how much heap you have left during your debugging.  However, according to the spec, the maximum available data for ATT is 255 - overhead. In the case of att_readRSP, the overhead is 1 byte, which means the max data that can be sent is 254 bytes. This means that the peer device will need to use Read Blob Request to read the remaining data. The specification goes into this in the following section: [BLUETOOTH CORE SPECIFICATION Version 5.1 | Vol 3, Part F, §3.4]

    Q4: The att_readRsp needs to be sent out in 30s. This is defined by the Bluetooth specification. (See Q2)

    Q5: I believe some of the questions mentioned here are answered by the content in Q3. An offset would need to be implemented to handle read_blobRsp. You are not able to respond with all 288 bytes because our stack will not allow that as that is over the maximum supported MTU size. The following line is present in the specification, which should answer one of your questions: "The Read By Type Request is used to obtain the values of attributes where the attribute type is known by the handle is not known"  [BLUETOOTH CORE SPECIFICATION Version 5.1 | Vol 3, Part F, §3.4.4.1]

    You may find the following useful:

    API Documentation: ATT_ReadReq()

    API Documentation : ATT_ReadRsp()

    API Documentation: ATT_ReadBlobReq()

    API Documentation: ATT_ReadBlobRsp()

    E2E Thread: https://e2e.ti.com/support/wireless-connectivity/bluetooth/f/bluetooth-forum/471864/ble-v2-1-gatt-read-data-not-ready-how-to-delay/1696116

    Regards,

    Jan

  • That 6-year-old E2E thread suffers from the same issue as the other 6-year-old E2E thread you linked me previously: it refers to some "Simple NP" project that is not part of any recent SDK.  I don't think you answered me before when I asked where to find that, so I'll ask again since this is the second time you've referred me to it.

    I hope it exists somewhere, because some example code that actually runs would be really nice.  It's not that I couldn't find any of the above API "documentation"; it's just that pointing repeatedly to a function prototype and its 4-word description isn't super helpful.

    This "dummy reply" is the most promising lead here, but, I don't really need links to the the BLE specification.  I have that.  And I'm not writing my own BLE stack.  I need to know what TI's BLE stack does because I'm writing a TI application.  So... obvious follow-up question... how do I use TI's SDK to send a "dummy reply"?  Can I just call ATT_ReadRsp() with a attReadRsp_t.len of 0 and a attReadRsp_t.pValue of NULL?  This is the kind of stuff that the API documentation doesn't really tell me.

    OR, actually, the API documentation lists an ATT_ErrorRsp() function.  In the stack-context service callback itself, if you don't return "blePending" you can return SUCCESS, or specific error messages like ATT_ERR_INVALID_OFFSET.  If I use blePending and my application encounters a problem, like not being able to allocate the memory for the response, can I just call ATT_ErrorRsp() to throw an ATT_ERR_INSUFFICIENT_RESOURCES, and that would suffice for the responding to the GATT client to let the connection continue?  Is there some reason that doesn't work?  Do I have to call ATT_Read*Rsp() with a "dummy reply"?

  • Hi Chris,

    The Simple NP project is part of the BLE-STACK version 2.2.5. The stack supports the CC2640/CC2650 devices. The Simple NP project is located there and may be useful to your use-case as it uses blePending throughout its application files. You can't send an empty attReadRsp_t as a "dummy reply." For a dummy reply, you can send 1 byte (0x00 or some other byte value) and set up some post-processing on the receiver side to identify the dummy value and do some further processing based on the dummy value (wait a bit longer or do some other processing while you wait for example).

    Regards,

    Jan

  • "On the receiver side"?  Once I've called ATT_Read*Rsp(), do I even have access to where it goes?  Isn't that all in the stack at that point?  Surely you're not talking about the central device that's connected as the GATT client that I have no control over?

    And what about the ATT_ErrorRsp() part which seems like it would be a better solution if it works like I asked above?

  • Hi Chris,

    I meant the peer device in the connection. I truly apologize, I thought that you were in control of both the central and peripheral in this instance. I have a few questions that I would like some clarity on. The other device needs to send a att read request, so if you are not in control of the central, then how is this occurring? If you are not in control of the central, then how would the central issue an att blob read? Can you clarify what you mean by having no control over the central device? I will look into the ATT_ErrorResp() and get back to you. Can you describe what behavior occurs in your use-case if the read_rsp is sent with a dummy value?

    Regards,

    Jan

  • The central is usually an Android or iOS smartphone.  We do have an app, but ours is not the only app that could talk to our peripheral.  Even if it was, it's not possible at a lower level to get see which packet out of a blob read had the dummy value as the OS handles this, nor is it trivial to figure out at a higher level whether any random byte in the data isn't real but rather the result of one of these dummy reads.  Putting the burden on the other end of the connection is not a solution.

    I have had some luck in my testing so far both with adding the length/offset/method parameters to my callback/message to handle multiple REQ types as well as using ATT_ErrorRsp().  If I set an MTU of 23 and try to read one of my longer characteristics, it was not working before, but now I am able to properly read it with ATT_READ_BLOB_REQ/ATT_ReadBlobRsp().  That was even without having added ATT_DELAYED_REQ yet, which I suspect is more for using GATTServApp_ReadRsp() instead of manually handling all the REQ types and calling GATT_bm_alloc() and ATT_Read*Rsp().

    I'm still unclear on the ATT_READ_BY_TYPE_REQ case.  I honestly don't even know when this type of request is made of an individual characteristic, and what data is supposed to be in the response.  Also, in the link you sent me as a documentation example (https://dev.ti.com/tirex/content/simplelink_cc13x2_26x2_sdk_4_40_04_04/docs/ble5stack/ble_user_guide/html/ble-stack-5.x/gatt.html#delaying-an-att-read-request), it's being handled like this:

     case ATT_READ_BY_TYPE_REQ:
        {
            Status = GATTServApp_ReadRsp( ConnHandle, pAttrValue, attrLen, attrHandle );
            break;
        }

    
    

    Ignoring that it's not clear what the value pointer is for that case since in the other cases the data had to be put together first and this one just pretends it already exists somewhere, look at that last parameter, "attrHandle".  That conflicts with the both the function prototype in the actual source code, and the API documentation here (dev.ti.com/.../group___g_a_t_t_serv_app.html), which both show that argument is supposed to be an offset, not an attribute handle:

    extern bStatus_t GATTServApp_ReadRsp( uint16 connHandle, uint8 *pValue, uint16 pLen, uint16 offset );

    This is what I mean when I say a pseudocode example isn't really helpful; it's clear that whoever wrote it did not intend for it to be a thorough solution.  The basic idea was helpful, but the details have to be filled in at some point if it's to actually run.

    As for ATT_ErrorRsp(), I'm able to send the error immediately with no 30-second timeout, receive it on the other side, and then continue to read other attributes without connection loss, which is exactly what I wanted.  I'm not 100% sure I have all the parameters correct, though.

     attErrorRsp_t rsp;
     rsp.reqOpcode = pData->method;
     rsp.handle = pData->pAttr->handle;
     rsp.errCode = ATT_ERR_INSUFFICIENT_RESOURCES;
     ATT_ErrorRsp(pData->connHandle, &rsp);

    The error code seems fine.  For the attribute handle I had to add gattAttribute_t *pAttr to the list of parameters from WriteAttrCB() to send in the message to my application task, but I think that's right.  The one I'm most unsure of is the reqOpcode; is that just the "method," ie, ATT_READ_REQ etc?

  • Hi Chris,

    I hope you are doing well. I consulted the team and have some answers to your questions. Regarding the ATT_READ_BY_TYPE_REQ case, this is issued when you don't know the attribute handle. Upon talking with the team, it seems the pseudocode is right and the doxygen is wrong. The main reason to enable ATT_DELAYED_REQ is to handle att_readByTypeReq. If this is not enabled and there is another central sending read by type requests (which contains starting handle/ending handle and attribute type that corresponds to the attribute which needs a delayed response), then the response won't be delayed, and the non real time value will be returned. If you don't need any random app to get the real-time value, then you don't need to enable the ATT_DELAYED_REQ.

    Upon reviewing the ATT_ErrorRsp() function with the team and your implementation of it. You are correct, the rsp.reqOpcode is just the method  that we are responding to with att_errorRsp. So in your case it would be ATT_READ_REQ. We believe your implementation is correct. and we expect no issues. However, if you see any unexpected behavior please let us know and we will take a closer look at this.

    Regards.

    Jan