Extremely weird error when writing to GATT Notify

Tip / Sign in to post questions, reply, level up, and achieve exciting badges. Know more

cross mob
Anonymous
Not applicable

I'm running into a very strange issue when trying to write some values to GATT. Basically I have a linked list of Events. I'm trying to run through the entire list and send each value to my smartphone. 

   

What's weird: 

   

My ble device will only send 10 notification calls. For instance, the output on my smartphone for a single event is the timestamp, a user code, and userID. It looks like this: 

   

2007-03-01T13:00:20 VS 2d97f036a1T13G21Jm0

   

 

   

So, if I try to have more say 4 events, the output should look like this: 

   

2007-03-01T13:00:20 VS 2d97f036a1T13G21Jm0
​2007-03-01T13:00:20 VS 2d97f036a1T13G21Jm1
​2007-03-01T13:00:20 VS 2d97f036a1T13G21Jm2
​2007-03-01T13:00:20 VS 2d97f036a1T13G21Jm3

   

 

   

​However all that I ever receive on my smartphone is this:

   

2007-03-01T13:00:20 VS 2d97f036a1T13G21Jm0
​2007-03-01T13:00:20 VS 2d97f036a1T13G21Jm1
​2007-03-01T13:00:20 VS 2d97f036a1T13G21Jm2
​2007-03-01T13:00:20 

   

 

   

Here is my function that loops through my linked list and send the data to a function to be printed: 

   
    

/**
 Print out all the currently recorded events.
 */
void printEvents() {

    

    UART_UartPutString("printing events");

    

    // Get the first item of our linked list
    struct Event* temp = front;

    

    // Counter to load and make sure we are getting
    // through our whole linked list.
    int counter = 0;

    

    // Run through the linked list. I print out all
    // the values of temp to ensure that each item
    // is being fully cycled through. This does
    // correctly print all the data out to UART.
    while(temp != NULL) {

    

        // Update our counter
        counter++;

    

        // Print out all the data to UART for verification
        UART_UartPutString("||");
        UART_UartPutString(temp->time);
        UART_UartPutString(temp->name);
        UART_UartPutString(temp->userId);
        UART_UartPutString("||");

    

        

    

        // Send our event to the smartphone
        printEvent(temp);

    

        // move to next item in list
        temp = temp->next;

    

    }

    

    // Convert our counter to something we can print to UART
    char* str = malloc(16);
    snprintf(str, 16, "%d", counter);

    

    

    

    // This correctly prints out 4 which it should be...
    UART_UartPutString("counter = ");
    UART_UartPutString(str);

    

    free(str);

    

    UART_UartPutString("done printing");

    

    // Our terminting value so app knows it can start parsing data.
    // This is never reached if we have more than 3 events in the queue.
    sendValueToGATT((uint8 *)"---", sizeof("---"));

    

}

   
   

 

   

So pretty straight forward. In the code above, all of the data from the linked list is sent and printed in UART correctly. Thus I know the entire list is being run through. Here is my printEvent function: 

   
    

/**
 Prints out a single event. A starting bit and ending bit will also be sent.

    

 @param event A pointer to the Event that needs to be send.
*/
void printEvent(struct Event *event) {
    sendValueToGATT((uint8 *)&event->time, sizeof(event->time));
    sendValueToGATT((uint8 *)&event->name, sizeof(event->name));
    sendValueToGATT((uint8 *)&event->userId, sizeof(event->userId));
}

   
   

 

   

And here is my sendValueToGATT function: 

   
    

/**
 Sends a given value over the GATT profile to the connected device.

    

 @param value A uint8 casted value to be sent
 @param size  The size of the value being passed. Usually aquired by using `sizeof`.
*/
void sendValueToGATT(uint8* value, uint16 size) {
    CYBLE_GATTS_HANDLE_VALUE_NTF_T     tempHandle3;

    

    tempHandle3.attrHandle = CYBLE_LEDCAPSENSE_CAPSENSE_CHAR_HANDLE;
    tempHandle3.value.val = value;
    tempHandle3.value.len = size;
    CyBle_GattsWriteAttributeValue(&tempHandle3,0,&cyBle_connHandle,CYBLE_GATT_DB_LOCALLY_INITIATED );
    CyBle_GattsNotification(cyBle_connHandle,&tempHandle3);
}

   
   

 

   

I really have no idea why only 10 transaction max are taking place?? Is it some type of time threshold? 

0 Likes
1 Solution
HeLi_263931
Level 8
Level 8
100 solutions authored 50 solutions authored 25 solutions authored

A GATT notification is not guaranteed to arrive (for such notification there is a different call). It might also be swallowed by the receiver (e.g. when your processing takes too long) I think.

   

IIRC there is a separate call to ensure delivery of the notification (or at least to get notified when its not delivered), but I cannot remember right now how its called.

View solution in original post

0 Likes
10 Replies
HeLi_263931
Level 8
Level 8
100 solutions authored 50 solutions authored 25 solutions authored

A GATT notification is not guaranteed to arrive (for such notification there is a different call). It might also be swallowed by the receiver (e.g. when your processing takes too long) I think.

   

IIRC there is a separate call to ensure delivery of the notification (or at least to get notified when its not delivered), but I cannot remember right now how its called.

0 Likes
Anonymous
Not applicable

I gotcha. Another option that I was thinking about was to create a different service for each piece of data that needs to be sent. So a service for time, value, date. Do you have any thoughts on that approach? 

0 Likes

Why don't you create a service whose attributes have all three values as fields? That way you can send one event with a single write call.

   

Or you can create an attribute as a larger array and send multiple values at once (e.g. once per second).

0 Likes
Anonymous
Not applicable

The notification should be delivered under normal circumstances, but you don't appear to be checking the return value of the CyBle_GattsNotification() call. That's where I would start for troubleshooting; don't proceed to the next packet unless this API call returns a success code (0).

   

The stack has limited buffer space for transmitting packets of any kind, and the link layer protocol cannot send any packets faster than once per 1.25ms. I suspect that you are filling the buffer very quickly, and then all subsequent attempts fail.

   

Also, note that the CyBle_GattsWriteAttributeValue() API call is not necessary if you only need to push data to a remote client. If you need to keep a local copy of the most recent value in RAM, you can keep that function call; it won't hurt anything to leave it there, but you can take it out if you don't need to update the local copy on the server.

Anonymous
Not applicable

Thank you so much for the response!! Do you happen to have a link to some documentation on how to check the return value of  CyBle_GattsNotification(), I'm having a hard time finding anything on that. 

0 Likes
Anonymous
Not applicable

The CyBle_GattsNotification() API method has a return type of CYBLE_API_RESULT_T, and you can find documentation about its expected possible return values in the BLE component datasheet (PDF) or API Documentation (context-sensitive help file). You can access either of these by right-clicking on the BLE component in the schematic from within PSoC Creator, then click on the relevant menu item.

   

In your case, you probably want to do something like the following:

   

while (CyBle_GetState() == CYBLE_STATE_CONNECTED &&
       CyBle_GattsNotification(cyBle_connHandle, &tempHandle3) != CYBLE_ERROR_OK)
{
    /* retry until successful */
    CyBle_ProcessEvents();
}

   

This is a blocking loop, but probably suitable in your case since there's no way to make the data transfer go any faster and you (presumably) can't afford to skip any data.

   

Concerning the different GATT structure you propose, there's no efficiency advantage to this approach. It won't hurt anything if you are already splitting those blocks of data into individual notification packets anyway, but there's no way to send multiple packets in parallel. You would still need to push notifications in sequence. That being said, if it would help the logical code layout of either the client-side or server-side implementation and you aren't trying to squeeze every last bit of throughput you can, then you can use whatever GATT structure makes the most sense to you as the designer.

   

Personally, I like to keep the GATT structure as simple as possible without resulting in an illogical structure (e.g. unrelated data crammed together into a single characteristic). The best efficiency comes from filling outgoing data packets to the MTU limit, typically 20 bytes per notification.

Anonymous
Not applicable

I'm assuming you mean I should update this method to look like the following: 

   

 

   

void sendValueToGATT(uint8* value, uint16 size) {

   

    CYBLE_GATTS_HANDLE_VALUE_NTF_T     tempHandle3;
    tempHandle3.attrHandle = CYBLE_LEDCAPSENSE_CAPSENSE_CHAR_HANDLE;
    tempHandle3.value.val = value;
    tempHandle3.value.len = size; 

   

    while (CyBle_GetState() == CYBLE_STATE_CONNECTED && CyBle_GattsNotification(cyBle_connHandle,&tempHandle3) != CYBLE_ERROR_OK) {

   

        CyBle_ProcessEvents();

   

    }
}

   

 

   

Trying the above I only am able to sent 1 full event then it gets stuck in the loop forever?? I've been able to log the error which is CYBLE_ERROR_INVALID_OPERATION. Which I was thinking that if it was a memory probably wouldn't it return a CYBLE_ERROR_MEMORY_ALLOCATION_FAILED?

0 Likes
Anonymous
Not applicable

Hmm. Looking at the rest of your loop all the way to the outermost function call, it looks like CyBle_ProcessEvents() isn't being called regularly except in the case where CyBle_GattsNotification() fails. I would suggest changing the while() { ... } loop do a do { ... } while () loop instead so that the loop body with CyBle_ProcessEvents() always executes at least once per attempt at sending a notification.

   

You may also want to look through this forum thread for information about a potentially related issue:

   

http://www.cypress.com/forum/proc-ble/problems-ble-notifications

Anonymous
Not applicable

So I was able to get my original output of almost all the data by doing this: 

   

 

   

    do {

   

         CyBle_ProcessEvents();

   

    } while (CyBle_GetState() == CYBLE_STATE_CONNECTED && CyBle_GattsNotification(cyBle_connHandle,&tempHandle3) != CYBLE_ERROR_OK && (CyBle_GattGetBusStatus() == CYBLE_STACK_STATE_FREE));

   

 

   

But it's still not all coming through 😞 I even tried adding in a delay as mentioned in the post you referenced which it still not working. I feel like we're so close!! 

   

So I added in a 100 ms delay thinking that each value would be sent with a 100 ms interval between. However, it seems the first 3 data points are sent with a delay, then a big chunk gets delivered...not sure if that means anything. 

   

   

0 Likes
Anonymous
Not applicable

So I was able to get this to work...but I have no idea why it works. I changed my main method loop to look like this: 

   

 

   

    int currentSendLocation = 0;
    struct Event* temp = front;

   

    for(;;)
    {  
         if (CyBle_GattGetBusStatus() == CYBLE_STACK_STATE_FREE && isSendingEvents && temp != NULL) {

   

            if (currentSendLocation == 0) {
                sendValueToGATT((uint8 *)&temp->time, sizeof(temp->time));
                currentSendLocation++;
            } else if (currentSendLocation == 1) {
                sendValueToGATT((uint8 *)&temp->name, sizeof(temp->name));
                currentSendLocation++;
            } else if (currentSendLocation == 2) {
                sendValueToGATT((uint8 *)&temp->userId, sizeof(temp->userId));
                currentSendLocation = 0;
                temp = temp->next;
             }

   

            if (temp == NULL) {
                sendValueToGATT((uint8 *)"---", sizeof("---"));
            }

   

            CyBle_ProcessEvents();
        }

   

        else {
             CyBle_ProcessEvents();
       }

   

        CyBle_EnterLPM(CYBLE_BLESS_DEEPSLEEP);
}

   

 

   

I literally have no idea why this is working vs my other method but so happy it does ha!