Help updating 'string' characteristic in BLE GATT - Adafruit Board, Bluefruit library

I'm using the Arduino IDE to program an Adafruit Feather Sense nRF52840 chip. I'm porting code works on an Arduino Nano and an ESP32. This project involves sending MCU data to a smartphone app via BLE GATT.

The problem is I the BLE Characteristic update won't work for a 'string' variable type.

I believe the issue is because I'm using std::string as a variable type but the Bluefruit.h library (Adafruits BLE library) doesn't seem to support this. The compiler returns the following error:

no matching function for call to 'BLECharacteristic::notify(std::string&)'

By using std::string all the data can be compressed into 1 BLE packet (18 bytes in total). This is important for the project. Here is a simplified version of the function to do this and do a BLE update:

void setAdvData() {
    std::string strServiceData = "";		

    strServiceData += (char)(transmission_num & 0xff);        // Lower byte of transmission number
    strServiceData += (char)((transmission_num >> 8) & 0xff); // Upper byte of transmission number
    strServiceData += (char)(temp & 0xff);                    // Lower byte of temperature
    strServiceData += (char)((temp >> 8) & 0xff);             // Upper byte of temperature
    strServiceData += (char)(humid & 0xff);                   // Lower byte of Humidity
    strServiceData += (char)((humid >> 8) & 0xff);            // Upper byte of Humidity

    myCharacteristic.notify(strServiceData);                 // Set characteristic message, and notify client
}

With this method each measurement only and always takes up 2 bytes, a lower byte and upper byte.

I have tried changing the variable type to 'String' (capital S) and to a 'char' array but these return similiar compilation errors:

no matching function for call to 'BLECharacteristic::notify(String&)'

I've also tried converting the infomarion using '.c_str()' but this results in a variable length greater than the size of 1 BLE packet, and corrupt data/strange behavior on the BLE central (smartphone). Also looked into understanding the differences between std::string, String, and char * but there are many conflicting answers on the Arduino forums and nothing yet leading to a solution

Any help or ideas would be great, thanks

PS: I have already asked this on the Adafruit Forums. There is a smaller community there, and time is a factor so I have posted this here too.

As a test, can you hard code a char array and pass that to notify?

Why use Strings in the first place rather than C style strings ?

Use the sprintf() function to build the string

Hardcoding this way will (mostly) work:

myCharacteristic.notify("HelloWorld");

Using the nRFConnect App this is what is received:

48-65-6c-6c-6f-57-6f-72-6c-64-d6-4e-6c-90-a4-97-73-35-34-12

Which translates to,

"HelloWorld�Nl���s54"

I'm unsure about where the extra characters are coming from.

Do you have a link to the library?

Here is the 'src' for the nRF52 Bluefruit library:

Bluefruit52Lib

And here is the specific .cpp and .h file for characteristics:

src/BLECharacteristic.cpp

src/BLECharacteristic.h

Well, the library purports to handle strings. That looks as though you somehow lost the terminating zero. You might also try casting it to void* and pass the string length as the second parameter.

Yes, this worked!

Anyway here's how I did it in code:

void setAdvData() {

char buffer[20];
  sprintf(buffer, "%c%c%c%c%c%c", 
                        (char)(trans_num & 0xff), (char)((trans_num >> 8) & 0xff), 
                        (char)(temp & 0xff), (char)((temp  >> 8) & 0xff), 
                        (char)(humid & 0xff), (char)((humid >> 8) & 0xff), 

    myCharacteristic.notify(buffer);                 // Set characteristic message, and notify client
}

Thanks @UKHeliBob !

PS: I found the additonal characters being received during testing was due to Bluefruit's way of initializing BLE characteristics. If a characteristic is initialized to a certain size, say a string of length 10, it will always send 10 characters. Even if you set a lower size in the .notify command (ie. myCharacteristic.notify(buffer, 2) ), another 8 characters will still be sent. From my brief testing these appear to be whatever was previously in the BLE packet

Actually there is a timing issue now. When I attempted to use c_str() method, though the data was corrupted I was able to get much faster transfer speeds through BLE.

Each BLE update which included 8 measurements on the MCU and transferring the packet took only 2 ms. Now with the new method of sprintf it is taking 15 ms, which is too slow.

I tried tried making char buffer[20] global, but this didn't help

Anyone have any ideas?

If using sprintf() is too slow you could try

buffer[0] = (char)(trans_num & 0xff);
buffer[1] = (char)((trans_num >> 8) & 0xff);
buffer[2] = (char)(temp & 0xff);
buffer[3] = (char)((temp  >> 8) & 0xff);
buffer[4] = (char)(humid & 0xff);
buffer[5] = (char)((humid >> 8) & 0xff);

I made a mistake. Turns out 'sprintf' isn't slowing down the code. Some timers in the code showed 'sprintf' didn't add anything measurable (ie >1 ms).

Previously when using 'String' and c_str(), and getting corrupt data, the code was able cycle and update the BLE message much faster. I have double checked this. The reason I now realize is that I had increased the MTU size and increased the size of the BLE update. This was removed for debugging and just trying to get correct data through. I forgot to put it back in once the initial sprintf try worked, and compare oranges to oranges.

But, I've tried to reimplement code to have a bigger MTU, and now I'm having the same issue as before. Faster BLE updates, but the data is corrupted. Hopefully someone can check my code below and give insight to where it is wrong.

What is most odd is that having a larger BLE characteristic message decreases the time it takes for myCharacteristic.notify(buffer); to complete. With a standard MTU size (< 20 bytes), the timer reports it takes ~13 ms to complete. With a larger MTU size (180 bytes), the timer shows it takes ~2 ms to complete.

This is very counterintuitive to me. I have double checked with my own application and with nRFConnect and both apps report the same times.

Here is my code for increasing the MTU size:

// Global variables
char buffer[7];                   // 6 bytes of information, 1 byte for null terminator
char longBuffer[61];              // 6x10 byte of information, 1 byte for null terminator
loop_count = 1;                   // Loop number

void setAdvData() {

  sprintf(buffer, "%c%c%c%c%c%c",      // 6 bytes per each message
                        (char)(trans_num & 0xff), (char)((trans_num >> 8) & 0xff), 
                        (char)(temp & 0xff), (char)((temp  >> 8) & 0xff), 
                        (char)(humid & 0xff), (char)((humid >> 8) & 0xff), 

  strcat(longBuffer, buffer);    // Add current loop values to longer buffer

  // Have a loop to have a message size 10x longer than before,
  // ie each BLE update has 10 measures for each sensor type (temp, humid, etc.)
  if (loop_count >= 10) {
    loop_count = 1;
    myCharacteristic.notify(longBuffer);       // Update BLE
    strcp(longBuffer, "");                     // Clear longBuffer
   }
   else {
   loop_count++;
   }

}

Second update:

I am helplessly perplexed by the Adafruit board's behavior. I decided to "triple-check" things so to speak and started by:

  1. Only sending a standard sized BLE message (<20 bytes)
  2. Using the methods suggested by @UKHeliBob (sprintf) and @cattledog (buffer array).

Quick results:

The sprintf method worked, but the char buffer method did not. As a sanity check, both methods were tested on the same script for an ESP32 and both worked flawlessly. Even with a standard MTU size, data speeds were more than adequate (6 ms for each loop and BLE update, with delays put it to slow it down).

Detailed results:

  1. The sprintf method works as expected. Data is sent + received correctly, albeit too slow for my needs. But it works
  2. The buffer array method doesn't work. Except for the trans_num (first 2 bytes in the BLE message), the others values don't correctly update in the .notify() packet. They are all replaced with the same respective numbers for random prolonged periods of time. For instance, humidity will repeatedly be recorded at 64228, a value it will never even measure at. Temperatre at 59823. Then for 3-8 seconds, the values will begin fluidly updating, before getting stuck on these odd values once more. Serial.print() confirms the measurements are being updated, but .notify() isn't updating them. It's very strange the trans_num is updating for each message and the rest aren't.

Distance and connection strength are not the issue, both sending+receiving devices are side-by-side, and one method works while the other does not.

I wonder if there is a deeper issue with the Adafruit board or the Bluefruit.h library...

Unfortunately I need an nRF52 board for it's low power consumption so I'm not sure how to progress. Advice is again appreciated

I have never used the Adafruit library. I have only used the ESP32 with its library and a Nano 33 BLE with the Arduino BLE library.

You may need to add a terminating null to the array like with the sprintf.
buffer[6] = '\0';

Alternatively, there may be a notification command for a byte array with a size variable included

myCharacteristic.notify(buffer, sizeof(buffer));

I have partly determined what the error is and now both methods work. For future reference both of these methods do work with Bluefruit for updating GATT string type variables, however they max out at ~1.2kB/s:

sprintf() method:

char buffer[7];       // Set this globally, 6 bytes + 1 terminating number
void setAdvData() {

  sprintf(buffer, "%c%c%c%c%c%c", 
                        (char)(trans_num & 0xff), (char)((trans_num >> 8) & 0xff), 
                        (char)(temp & 0xff), (char)((temp  >> 8) & 0xff), 
                        (char)(humid & 0xff), (char)((humid >> 8) & 0xff), 

    myCharacteristic.notify(buffer, sizeof(buffer));                 // Set characteristic message, and notify client
}

char array method:

char buffer[7];       // Set this globally

void setAdvData() {

    buffer[0] = (char)(trans_num & 0xff);
    buffer[1] = (char)((trans_num >> 8) & 0xff);
    buffer[2] = (char)(temp & 0xff);
    buffer[3] = (char)((temp  >> 8) & 0xff); 
    buffer[4] = (char)(humid & 0xff);
    buffer[5] = (char)((humid >> 8) & 0xff);

    myCharacteristic.notify(buffer, sizeof(buffer));                 // Set characteristic message, and notify client
}

The key to getting this to work is it not use the following line when setting up BLE characteristics:

  myCharacteristic.setFixedLen(sizeof(buffer));

This is line of code is used in several of Adafruit's BLE examples, and for some reason it does not play nice. Even if you pass the .notify() command a sizeof(buffer) input, the size will be ignored. The best thing is to leave it out all together. I still don't know why it worked with one method and not the other, but I'll assume it was a coding error somewhere on my part

Interestingly a terminating null is not needed for the buffer strings. I have tried with and without and there is no difference. It must be created when the string is created globally, and as this location in the string is never modified, it remains that way.

I have still not sorted out the throughput limitations, but that is a separate issue I will create a new thread about elsewhere. If anyone should need help regarding this in the future, this reference on strings for Arduino is a great place to start

Thanks to everyone on their help with this

When using the sprintf() method of building the string is it necessary to cast the variables to char bearing in mind that sprintf() will convert them to chars anyway given the right formatting character ?

I discovered an ArduinoBLE thing today, that might be related

I dynamically construct the service UUID.. as the chars have meaning
I do this in a subroutine, and use a character buffer char [36], to construct the string and then use it to create the service..
and then exit the routine..

sometime later I startAdvertising an get junk all over the place...

well, DOAH, the functions that used the service UUID did NOT COPY the value, but used the pointer I provided.. which was program stack, and has been trashed by the time I made the advertise call.. oops..

moving the character buffer up to the global variables and changing no code made all the issues go away..

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.