ESP32 BLE send/receive data as uint32_t

Hi,

I am using a esp32 dev board and ble. I have a working example on how to send and receive data as strings.

The sending part does
pRemoteCharacteristic->writeValue(newValue.c_str(), newValue.length());

and on the receiving part we have
std::string value = pCharacteristic->getValue();

So this works without issue.
But I do not want to send strings.
I have a uint32_t value of which every bit encodes a specific information.
I want to send those exact 32 bits over BLE and receive them on the other end. Read them bit by bit.

Does anyone have some hints on how to do this?
Thanks.

You will use syntax along the lines of

setValue( ( uint8_t* )&myuint32, 4 );

or

writeValue(( uint8_t* )&myuint32, 4 );

Read them bit by bit.

You are going to have to read the value into a uint_32 variable and then do the bitwise processing.

If you need additional assistance, it will be best for you to provide the complete codes for the server and central.

Hi cattledog,

thank you for your help! Unfortunately I do need a few more hints. How exactly am I reading the values into a uint_32 variable?

Here is my code
server:

/*
    Based on Neil Kolban example for IDF: https://github.com/nkolban/esp32-snippets/blob/master/cpp_utils/tests/BLE%20Tests/SampleServer.cpp
    Ported to Arduino ESP32 by Evandro Copercini
    updates by chegewara

*/

#include <BLEDevice.h>
#include <BLEUtils.h>
#include <BLEServer.h>

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

#define SERVICE_UUID        "1f507470-0d15-4f7f-a2a9-059ea6f69834"
#define CHARACTERISTIC_UUID "5a1f4286-8367-458f-b65d-08cf01671c7f"



class MyCallbacks: public BLECharacteristicCallbacks {
    void onWrite(BLECharacteristic *pCharacteristic) {
     
     
     std::string value = pCharacteristic->getValue();

     uint32_t int_val = (uint32_t) value.c_str();

      Serial.println("some value arrived");
      print_binary(int_val);
    }

    bool get_bit(uint32_t num, uint32_t position)
    {
      bool bit = num & (1 << position);
      return bit;
    }

    void print_binary(uint32_t num)
    {
      Serial.print("binary: ");
      for(int i = 31; i >= 0; i--)
      {
        Serial.print(get_bit(num, i));
      }
      Serial.println(" -- ");
    }
};

void setup() {
  Serial.begin(115200);
  Serial.println("Starting BLE work!");

  BLEDevice::init("Long name works now");
  BLEServer *pServer = BLEDevice::createServer();
  BLEService *pService = pServer->createService(SERVICE_UUID);
  BLECharacteristic *pCharacteristic = pService->createCharacteristic(
                                         CHARACTERISTIC_UUID,
                                         BLECharacteristic::PROPERTY_READ |
                                         BLECharacteristic::PROPERTY_WRITE
                                       );
                                       
  pCharacteristic->setCallbacks(new MyCallbacks()); 
  pCharacteristic->setValue("Hello World says Neil");
  pService->start();
  // BLEAdvertising *pAdvertising = pServer->getAdvertising();  // this still is working for backward compatibility
  BLEAdvertising *pAdvertising = BLEDevice::getAdvertising();
  pAdvertising->addServiceUUID(SERVICE_UUID);
  pAdvertising->setScanResponse(true);
  pAdvertising->setMinPreferred(0x06);  // functions that help with iPhone connections issue
  pAdvertising->setMinPreferred(0x12);
  BLEDevice::startAdvertising();
  Serial.println("Characteristic defined! Now you can read it in your phone!");
}

void loop() {
  // put your main code here, to run repeatedly:
  delay(2000);
}

client:

#include "BLEDevice.h"


// The remote service we wish to connect to.
static BLEUUID serviceUUID("1f507470-0d15-4f7f-a2a9-059ea6f69834");
// The characteristic of the remote service we are interested in.
static BLEUUID    charUUID("5a1f4286-8367-458f-b65d-08cf01671c7f");

static boolean doConnect = false;
static boolean connected = false;
static boolean doScan = false;
static BLERemoteCharacteristic* pRemoteCharacteristic;
static BLEAdvertisedDevice* myDevice;

static void notifyCallback(
  BLERemoteCharacteristic* pBLERemoteCharacteristic,
  uint8_t* pData,
  size_t length,
  bool isNotify) {
    Serial.print("Notify callback for characteristic ");
Serial.print(pBLERemoteCharacteristic->getUUID().toString().c_str());
    Serial.print(" of data length ");
    Serial.println(length);
    Serial.print("data: ");
    Serial.write(pData, length);
    Serial.println();
}

class MyClientCallback : public BLEClientCallbacks {
  void onConnect(BLEClient* pclient) {
  }

  void onDisconnect(BLEClient* pclient) {
    connected = false;
    Serial.println("onDisconnect");
  }
};

bool connectToServer() {
    Serial.print("Forming a connection to ");
    Serial.println(myDevice->getAddress().toString().c_str());

    BLEClient*  pClient  = BLEDevice::createClient();
    Serial.println(" - Created client");

    pClient->setClientCallbacks(new MyClientCallback());

    // Connect to the remove BLE Server.
    pClient->connect(myDevice);  // if you pass BLEAdvertisedDevice instead of address, it will be recognized type of peer device address(public or private)
    Serial.println(" - Connected to server");
    pClient->setMTU(517); //set client to request maximum MTU from server (default is 23 otherwise)

    // Obtain a reference to the service we are after in the remote BLE server.
    BLERemoteService* pRemoteService = pClient->getService(serviceUUID);
    if (pRemoteService == nullptr) {
      Serial.print("Failed to find our service UUID: ");
      Serial.println(serviceUUID.toString().c_str());
      pClient->disconnect();
      return false;
    }
    Serial.println(" - Found our service");


    // Obtain a reference to the characteristic in the service of the remote BLE server.
    pRemoteCharacteristic = pRemoteService->getCharacteristic(charUUID);
    if (pRemoteCharacteristic == nullptr) {
      Serial.print("Failed to find our characteristic UUID: ");
      Serial.println(charUUID.toString().c_str());
      pClient->disconnect();
      return false;
    }
    Serial.println(" - Found our characteristic");

    // Read the value of the characteristic.
    if(pRemoteCharacteristic->canRead()) {
      std::string value = pRemoteCharacteristic->readValue();
      Serial.print("The characteristic value was: ");
      Serial.println(value.c_str());
    }

    if(pRemoteCharacteristic->canNotify())
      pRemoteCharacteristic->registerForNotify(notifyCallback);

    connected = true;
    return true;
}
/**
 * Scan for BLE servers and find the first one that advertises the
service we are looking for.
 */
class MyAdvertisedDeviceCallbacks: public BLEAdvertisedDeviceCallbacks {
 /**
   * Called for each advertising BLE server.
   */
  void onResult(BLEAdvertisedDevice advertisedDevice) {
    Serial.print("BLE Advertised Device found: ");
    Serial.println(advertisedDevice.toString().c_str());

    // We have found a device, let us now see if it contains the service we are looking for.
    if (advertisedDevice.haveServiceUUID() && advertisedDevice.isAdvertisingService(serviceUUID)) {

      BLEDevice::getScan()->stop();
      myDevice = new BLEAdvertisedDevice(advertisedDevice);
      doConnect = true;
      doScan = true;

    } // Found our server
  } // onResult
}; // MyAdvertisedDeviceCallbacks


void setup() {
  Serial.begin(115200);
  Serial.println("Starting Arduino BLE Client application...");
  BLEDevice::init("");

  // Retrieve a Scanner and set the callback we want to use to be informed when we
  // have detected a new device.  Specify that we want active scanning and start the
  // scan to run for 5 seconds.
  BLEScan* pBLEScan = BLEDevice::getScan();
  pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks());
  pBLEScan->setInterval(1349);
  pBLEScan->setWindow(449);
  pBLEScan->setActiveScan(true);
  pBLEScan->start(5, false);
} // End of setup.


// This is the Arduino main loop function.
void loop() {
  // If the flag "doConnect" is true then we have scanned for and found the desired
  // BLE Server with which we wish to connect.  Now we connect to it. Once we are
  // connected we set the connected flag to be true.
  if (doConnect == true) {
    if (connectToServer()) {
      Serial.println("We are now connected to the BLE Server.");
    } else {
      Serial.println("We have failed to connect to the server; there is nothin more we will do.");
    }
    doConnect = false;
  }

  // If we are connected to a peer BLE Server, update the characteristic each time we are reached
  // with the current time since boot.
  if (connected) {
    update_state();
  }else if(doScan){
    BLEDevice::getScan()->start(0);  // this is just example to start scan after disconnect, most likely there is better way to do it in arduino
  }

  delay(1000); // Delay a second between loops.
  Serial.println("loopin");
} // End of loop



void send_state(uint32_t state)
{
  print_binary(state);
    String newValue = "Time since boot: " + String(millis()/1000);
    Serial.println("Setting new characteristic value to \"" + newValue+ "\"");

    // Set the characteristic's value to be the array of bytes that is actually a string.
    //pRemoteCharacteristic->writeValue(newValue.c_str(), newValue.length());
    pRemoteCharacteristic->writeValue((uint8_t*)&state, 4);
}



void update_state()
{
  uint32_t current_state = 0b00000000100000000100000000000000;//get_new_state();
  send_state(current_state);
}





bool get_bit(uint32_t num, uint32_t position)
{
  bool bit = num & (1 << position);
  return bit;
}



void print_binary(uint32_t num)
{
  Serial.print("binary: ");
  for(int i = 31; i >= 0; i--)
  {
    Serial.print(get_bit(num, i));
  }
  Serial.println(" -- ");
}

edit: the issue is, the value I read is different from the one i send, so I am doing it wrong.

I'm not familiar, but looking at the source code on the BLE GitHub, I'd say check out the getData() and getLength() functions for handling binary data.

Hi gfvalvo,

dang, thank you for the obvious hint to digging through the repository.
I did that as well and found parts where it internally reads an uint32_t.
I grabbed the interesting parts - it works!

So the server looks like this now:

/*
    Based on Neil Kolban example for IDF: https://github.com/nkolban/esp32-snippets/blob/master/cpp_utils/tests/BLE%20Tests/SampleServer.cpp
    Ported to Arduino ESP32 by Evandro Copercini
    updates by chegewara

*/

#include <BLEDevice.h>
#include <BLEUtils.h>
#include <BLEServer.h>

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

#define SERVICE_UUID        "1f507470-0d15-4f7f-a2a9-059ea6f69834"
#define CHARACTERISTIC_UUID "5a1f4286-8367-458f-b65d-08cf01671c7f"



class MyCallbacks: public BLECharacteristicCallbacks {
    void onWrite(BLECharacteristic *pCharacteristic) {
     
     
     std::string value = pCharacteristic->getValue();
     if (value.length() >= 4) {
     uint32_t int_val = *(uint32_t*)(value.data());
      Serial.println("some value arrived");
      print_binary(int_val);
     }
     else
     {
      Serial.println("data was not uint32");
     }
    }

    bool get_bit(uint32_t num, uint32_t position)
    {
      bool bit = num & (1 << position);
      return bit;
    }

    void print_binary(uint32_t num)
    {
      Serial.print("binary: ");
      for(int i = 31; i >= 0; i--)
      {
        Serial.print(get_bit(num, i));
      }
      Serial.println(" -- ");
    }
};

void setup() {
  Serial.begin(115200);
  Serial.println("Starting BLE work!");

  BLEDevice::init("Long name works now");
  BLEServer *pServer = BLEDevice::createServer();
  BLEService *pService = pServer->createService(SERVICE_UUID);
  BLECharacteristic *pCharacteristic = pService->createCharacteristic(
                                         CHARACTERISTIC_UUID,
                                         BLECharacteristic::PROPERTY_READ |
                                         BLECharacteristic::PROPERTY_WRITE
                                       );
                                       
  pCharacteristic->setCallbacks(new MyCallbacks()); 
  pCharacteristic->setValue("Hello World says Neil");
  pService->start();
  // BLEAdvertising *pAdvertising = pServer->getAdvertising();  // this still is working for backward compatibility
  BLEAdvertising *pAdvertising = BLEDevice::getAdvertising();
  pAdvertising->addServiceUUID(SERVICE_UUID);
  pAdvertising->setScanResponse(true);
  pAdvertising->setMinPreferred(0x06);  // functions that help with iPhone connections issue
  pAdvertising->setMinPreferred(0x12);
  BLEDevice::startAdvertising();
  Serial.println("Characteristic defined! Now you can read it in your phone!");
}

void loop() {
  // put your main code here, to run repeatedly:
  delay(2000);
}

This works but I am guessing this is still not the way to go. If someone has a clean/documented solution, I would appreciate it.

Do you mean this???

Not only is that ugly, but it's using a pointer for type punning which will illicit undefined behavior. Maybe try something like:

  uint32_t int_val;
  if (pCharacteristic->getLength() >= sizeof(int_val)) {
    memcpy(&int_val, pCharacteristic->getData(), sizeof(int_val));
    Serial.printf("Got Data: %d\n", int_val);
  } else {
    Serial.println("data was not uint32");
  }

This appears to print out the uint32_t value sent.

class MyCallbacks: public BLECharacteristicCallbacks {
    void onWrite(BLECharacteristic *pCharacteristic) {
     
     
     std::string value = pCharacteristic->getValue();
     if(value.length() >=4){
     Serial.println("some uint32_t value arrived");
     uint32_t int_val = 0;
     memcpy(&int_val,value.c_str(),4);   
     print_binary(int_val);
     }
     else
     {
      Serial.println("data was not uint32");
     }
    }

    bool get_bit(uint32_t num, uint32_t position)
    {
      bool bit = num & (1 << position);
      return bit;
    }

    void print_binary(uint32_t num)
    {
      Serial.print("binary: ");
      for(int i = 31; i >= 0; i--)
      {
        Serial.print(get_bit(num, i));
      }
      Serial.println(" -- ");
    }
};
18:14:09.290 -> some uint32_t value arrived
18:14:09.290 -> binary: 00000000100000000100000000000000 -

Hi guys,

first of all: thank you very much for taking the time to help me! It is very appreciated.
Both of your versions work and my problem is solved!

@gfvalvo : Yes, that part. It already stroke me as hacky, but my knowledge of C++ (near 0) did not allow me to really judge this. But this is how the library itself does it, see BLERemoteCharacteristic::readUInt32().

Is this a real concern or just very bad style - i.e., can this break when things around that library change?

The rules are so complex that I would avoid this construct all together.

The getValue() function returns a 'const char *'. So casting it to a 'uint32_t *' and then dereferencing it would illicit undefined behavior as described in the very last example here: https://tttapa.github.io/Pages/Programming/Cpp/Practices/type-punning.html
Not to mention that you're also casting away the 'const' qualifier!!!

Here's another read: https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#c183-dont-use-a-union-for-type-punning

If you're the pedantic type, see items 5 & 6 here: https://en.cppreference.com/w/cpp/language/reinterpret_cast

Hi @gfvalvo ,

ok, thanks for the links. Well, I can not really avoid it as the BLE library itself seems to use those - so I can only hope that they know what they are doing or drop the library.

I will have faith for now.

And of course I will stay away from those constructs in my own code. It is never a good idea to use something you don't understand anyway.

I'm interested in why you chose the architecture that you did.

It certainly works the way you chose, but typically the server/peripheral is posting changing data and it is read by the central/client.

In your code, the server is reading the long value from the client and processing the bits. The server does not appear to be sending values in loop.

Is there a reason behind your choice of roles for the client and server?

Hi @cattledog ,

there is a reason, this might totally change though. From what I have understood after a quick first research is that the server code is doing more heavy lifting, thus I am assuming it is using more power.

I will have two devices and the sending one will run on batteries. So I want the lowest power consumption on that end.

Plus I want to be able to reinitiate a fresh connection with the device I am holding in case it somehow gets lost, this again is the sending part.

I will have to do much more reading to decide for sure though. I wanted have some prototype first and optimize later if needed.

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