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.
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.
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.
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!!!
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.
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.