ESP32 Arduino BLE how to read hex data

Hello. I am using BLE for my project and have 2 services:

  1. Write
  2. Read notify

I am performing simple task - I am writing some data to write service and then I read the device response using read service.

I am aware that this can be easily achieved using bluetooth classic but I want to understand this and learn what is the issue in my program.

When I write to WRITE service, it executes the following callback:


class MyCallbacks: public BLECharacteristicCallbacks {

    void onWrite(BLECharacteristic *pCharacteristic)
    {

      std::string rxValue = pCharacteristic->getValue();
      Serial.print("value received = ");
      Serial.println(rxValue.c_str());

    }

};

My issue:

std::string rxValue = pCharacteristic->getValue();
Serial.print("value received = ");
Serial.println(rxValue.c_str());

I am not able to receive the data correctly when I send the data in hex format. I am using lightblue app to send data.

When sending hex data to the device, the serial monitor prints unknown symbols, how can this be fixed?

I am ble to send the data without any issues in string format.

A BLE characteristic is just a collection of bytes. You are storing these bytes as Strings. Strings are a collection of bytes with a formatting. Every byte is decodes using ASCII.

When you use Lightblue and send as String the number 1 you send a byte with value 0x31. When you send it as HEX aka byte value of 0x01 you send a Control Code that may be invisible or some weird symbol.

Try sending 4B as HEX and you should get the letter K printed.

You need to store the bytes you get from the characteristic as whatever type you need it to be. When our type is multiple bytes aka uint32_t (unsigned long) you need to send 4 bytes as HEX in the LightBlue app. When you use Serial.print the unsigned long will be converted into ASCII characters again to make it a human readable number.

Yes indeed you were right about the 4B and receiving the letter "k" , however, that is still unclear for me how can I store the data as hex.

What is most confusing to me, is that BLE getValue() method returns std::string:

std::string rxValue = pCharacteristic->getValue();

So does not matter what data format I sent, the dat will still be received as string.

I have tried to use otai() function but with no luck:

class MyCallbacks: public BLECharacteristicCallbacks {

    void onWrite(BLECharacteristic *pCharacteristic)
    {

      std::string rxValue = pCharacteristic->getValue();
      Serial.print("value received = ");
      Serial.println(rxValue.c_str());
      //TRYING TO USE ATOI
      int n = atoi(rxValue.c_str());
      Serial.print("ATOI result = ");
      Serial.println(n);
    }

};

Now the serial monitor prints :

14:51:09.702 -> value received = K
14:51:09.702 -> ATOI result = 0

I have also tried to use strtol() function:

class MyCallbacks: public BLECharacteristicCallbacks {

    void onWrite(BLECharacteristic *pCharacteristic)
    {
      char *ptr;


      //std::string rxValue = pCharacteristic->getValue();
      std::string rxValue = pCharacteristic->getValue();
      Serial.print("value received = ");
      Serial.println(rxValue.c_str());
      //TRYING TO USE ATOI
      uint16_t n = atoi(rxValue.c_str());
      Serial.print("ATOI result = ");
      Serial.println(n,HEX);
      //Trying to use strtol()
      long n2 = strtol(rxValue.c_str(),NULL,16);
      Serial.print("strtol1 results =");
      Serial.println(n2);

      long n3 = strtol(rxValue.c_str(),&ptr,16);
      Serial.print("strtol2 results =");
      Serial.println(n3);

      
   

    }

};

Serial monitor output:

15:05:30.253 -> value received = K
15:05:30.253 -> ATOI result = 0
15:05:30.253 -> strtol1 results =0
15:05:30.253 -> strtol2 results =0

I mostly use the ArduinoBLE library with the original Arduinos with BLE support. That library is easier to use but will not work for other devices. So, we will have to figure this out together.

I looked into the source for the ESP32 library. file: BLECharacteristic.cpp

There is a function called uint8_t* BLECharacteristic::getData() that will give you the raw data.

You will need to store the data into a variable of the required type.

It's not clear what you are asking. All data is in computer registers as binary. You have 01001011 in some memory location. It can be displayed as decimal 75, char 'K', or hex value 0x4F.

I'm not exactly clear on what is being sent, but you can display it as HEX with this

class MyCallbacks: public BLECharacteristicCallbacks {
    void onWrite(BLECharacteristic *pCharacteristic) {
      std::string rxValue = pCharacteristic->getValue();
      if (rxValue.length() > 0) {
        Serial.print("Received Value: ");
        for (int i = 0; i < rxValue.length(); i++)
          Serial.print(rxValue[i],HEX);

        Serial.println();
    }
};

Thanks both for the responses..i will try both these methods on monday

@cattledog The difference comes from the BLE app. There are two ways to write a value to a characteristic. One allows to write some String (you could call it ASCII mode) then you can simply use pCharacteristic->getValue() and the second is writing some HEX value (and that could be called binary mode) then you should use pCharacteristic->getData() and write the data to a variable of some type depending on how many HEX values you wrote in the app. You must be careful with the byte order in "binary mode". LSB is send first.

Thanks for the response. It does work using the getData() method, however there is another problem. I have found someone else talking about it here:

getData() method returns uint8_t* therefore I can only send 8 bits or less.

From what I can see, I can either:

  1. Ensure all my API is 8 bits or less

  2. Use string data format, then I can send whatever I want

This method also works, the only unconvenient thing about this method is that I will need to implement some extra steps. Since hex letters are read one by one, I need to append full message to a buffer before I can do match comparison. Anyways, thank you for helping out

This is out of the topic but I would appreciate if you could help me understand how these classes for callbacks are written:

class MyServerCallbacks: public BLEServerCallbacks {
    void onConnect(BLEServer* pServer) {
      deviceConnected = true;
    };

    void onDisconnect(BLEServer* pServer) {
      deviceConnected = false;
    }
};

class MyCallbacks: public BLECharacteristicCallbacks {

    void onWrite(BLECharacteristic *pCharacteristic)
    {
      uint8_t* received_data = pCharacteristic->getData();
      Serial.println(*received_data,HEX);
    }

};

Few questions:

  1. I am yet just learning about classes and inheritance, but I cant understand why we need this part:
class MyServerCallbacks: **public BLEServerCallbacks** {

Can I not just simply declare my class as following:

class MyServerCallbacks: {
    void onConnect(BLEServer* pServer) {
      deviceConnected = true;
    };

What exactly does public BLEServerCallbacks provide me?

  1. From what I understand, I cannot use any custom name functions on callbacks. All functions are hardcoded in the library such as onConnect, on Disconnect, onWrite and etc?

My full code is here:

/*
    Video: https://www.youtube.com/watch?v=oCMOYS71NIU
    Based on Neil Kolban example for IDF: https://github.com/nkolban/esp32-snippets/blob/master/cpp_utils/tests/BLE%20Tests/SampleNotify.cpp
    Ported to Arduino ESP32 by Evandro Copercini

   Create a BLE server that, once we receive a connection, will send periodic notifications.
   The service advertises itself as: 6E400001-B5A3-F393-E0A9-E50E24DCCA9E
   Has a characteristic of: 6E400002-B5A3-F393-E0A9-E50E24DCCA9E - used for receiving data with "WRITE"
   Has a characteristic of: 6E400003-B5A3-F393-E0A9-E50E24DCCA9E - used to send data with  "NOTIFY"

   The design of creating the BLE server is:
   1. Create a BLE Server
   2. Create a BLE Service
   3. Create a BLE Characteristic on the Service
   4. Create a BLE Descriptor on the characteristic
   5. Start the service.
   6. Start advertising.

   In this example rxValue is the data received (only accessible inside that function).
   And txValue is the data to be sent, in this example just a byte incremented every second.
*/
#include <BLEDevice.h>
#include <BLEServer.h>
#include <BLEUtils.h>
#include <BLE2902.h>

BLEServer *pServer = NULL;
BLECharacteristic * pTxCharacteristic;
bool deviceConnected = false;
bool oldDeviceConnected = false;
uint8_t txValue = 10;

// See the following for generating UUIDs:
// https://www.uuidgenerator.net/

#define SERVICE_UUID           "6E400001-B5A3-F393-E0A9-E50E24DCCA9E" // UART service UUID
#define CHARACTERISTIC_UUID_RX "6E400002-B5A3-F393-E0A9-E50E24DCCA9E"
#define CHARACTERISTIC_UUID_TX "6E400003-B5A3-F393-E0A9-E50E24DCCA9E"


class MyServerCallbacks: public BLEServerCallbacks {
    void onConnect(BLEServer* pServer) {
      deviceConnected = true;
    };

    void onDisconnect(BLEServer* pServer) {
      deviceConnected = false;
    }
};

class MyCallbacks: public BLECharacteristicCallbacks {

    void onWrite(BLECharacteristic *pCharacteristic)
    {
      uint8_t* received_data = pCharacteristic->getData();
      Serial.println(*received_data,HEX);
    }

};


void setup() {
  Serial.begin(115200);

  // Create the BLE Device
  BLEDevice::init("UART Service");

  // Create the BLE Server
  pServer = BLEDevice::createServer();
  pServer->setCallbacks(new MyServerCallbacks());

  // Create the BLE Service
  BLEService *pService = pServer->createService(SERVICE_UUID);

  // Create a BLE Characteristic
  pTxCharacteristic = pService->createCharacteristic(
                        CHARACTERISTIC_UUID_TX,
                        BLECharacteristic::PROPERTY_READ |
                        BLECharacteristic::PROPERTY_NOTIFY
                      );
  pTxCharacteristic->addDescriptor(new BLE2902());



  BLECharacteristic * pRxCharacteristic = pService->createCharacteristic(
      CHARACTERISTIC_UUID_RX,
      BLECharacteristic::PROPERTY_WRITE
                                          );
  pRxCharacteristic->addDescriptor( new BLE2902() );



  pRxCharacteristic->setCallbacks(new MyCallbacks());

  // Start the service
  pService->start();

  // Start advertising
  pServer->getAdvertising()->start();
  Serial.println("Waiting a client connection to notify...");
}

void loop() {
  /*
      if (deviceConnected) {
          pTxCharacteristic->setValue(&txValue, 1);
          pTxCharacteristic->notify();
          txValue++;
  		delay(10); // bluetooth stack will go into congestion, if too many packets are sent
  	}
  */


  // disconnecting
  if (!deviceConnected && oldDeviceConnected) {
    delay(500); // give the bluetooth stack the chance to get things ready
    pServer->startAdvertising(); // restart advertising
    Serial.println("start advertising");
    oldDeviceConnected = deviceConnected;
  }
  // connecting
  if (deviceConnected && !oldDeviceConnected) {
    // do stuff here on connecting
    oldDeviceConnected = deviceConnected;
  }
}

The full message is already in the rxValue.

Oh yes you are right. Thanks :slight_smile: