ESP32 BLE on batteries: can't read characteristics

Hello,

I am working on a portable temperature and humidity sensor (ESP32 WROOM + SHT11 temperature and humidity sensor) that sends data over BLE for a few seconds and then deep-sleeps for longer periods. The sensor is on two 3.7 V batteries and my goal is to make it last 10-20 days. The client is also an ESP32 WROOM.

I've tried different coding approach and finally found something that allows for peripheral deep sleeps and connection/disconnection on the client side without crashes and loss of data.

However, I'm now facing the problem that I am not able to read the characteristics sent by the peripheral on the client side. The peripheral prints the temperature and humidity readings and sends them over BLE correctly. The client sees the peripheral, its characteristics and confirms that the characteristics can notify, but I am not able to print the content of the characteristics.

Code for the server:

#include <BLEDevice.h>
#include <BLEUtils.h>
#include <BLEServer.h>
#include <BLE2902.h>
#include <SPI.h>
#include <Wire.h>
#include <SHT21.h>

//BLE server name
#define bleServerName "BME280_ESP32"

/* Define deep sleep options */
uint64_t uS_TO_S_FACTOR = 1000000;  // Conversion factor for micro seconds to seconds
uint64_t TIME_TO_SLEEP = 60;

// Timer variables
unsigned long lastTime = 0;
unsigned long timerDelay = 5000;

bool deviceConnected = false;

SHT21 sht;
float t;
float h;
int timeOut;
float TcorrectionSHT21 = 0;
int BLEtries = 5;

#define SERVICE_UUID "91bad492-b950-4226-aa2b-4ede9fa42f59"

BLECharacteristic temperatureCharacteristic("cba1d466-344c-4be3-ab3f-189f80dd7518", BLECharacteristic::PROPERTY_NOTIFY);
BLEDescriptor temperatureDescriptor(BLEUUID((uint16_t)0x2902));

BLECharacteristic humidityCharacteristic("ca73b3ba-39f6-4ab3-91ae-186dc9577d99", BLECharacteristic::PROPERTY_NOTIFY);
BLEDescriptor humidityDescriptor(BLEUUID((uint16_t)0x2903));

//Setup callbacks onConnect and onDisconnect
class MyServerCallbacks : public BLEServerCallbacks {
  void onConnect(BLEServer *pServer) {
    deviceConnected = true;
  };
  void onDisconnect(BLEServer *pServer) {
    deviceConnected = false;
  }
};


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

  /* Enable Timer wake_up */
  esp_sleep_enable_timer_wakeup(TIME_TO_SLEEP * uS_TO_S_FACTOR);

  /* Initialise sensor */
  sht.begin();

  /* Initialize Bluetooth communication. */
  BLEDevice::init(bleServerName);
  // esp_err_t errRc = esp_ble_tx_power_set(ESP_BLE_PWR_TYPE_DEFAULT, ESP_PWR_LVL_P9);
  esp_ble_tx_power_set(ESP_BLE_PWR_TYPE_ADV, ESP_PWR_LVL_P9);
  esp_ble_tx_power_set(ESP_BLE_PWR_TYPE_SCAN, ESP_PWR_LVL_P3);

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

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

  /* Add characteristics for temperature and humidity. */
  bmeService->addCharacteristic(&temperatureCharacteristic);
  temperatureDescriptor.setValue("BME temperature");
  temperatureCharacteristic.addDescriptor(&temperatureDescriptor);

  bmeService->addCharacteristic(&humidityCharacteristic);
  humidityDescriptor.setValue("BME humidity");
  humidityCharacteristic.addDescriptor(new BLE2902());

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

  // Start advertising
  BLEAdvertising *pAdvertising = BLEDevice::getAdvertising();
  pAdvertising->addServiceUUID(SERVICE_UUID);
  pServer->getAdvertising()->start();
  Serial.println("Waiting a client connection to notify...");
  delay(500);
  readsendTBLE();
  Serial.println("DONE! Going to sleep now.");
  esp_deep_sleep_start();
}
void loop() {
  //Empty
}

void readsendTBLE() {
  if ( deviceConnected ) {
    Serial.println("Device is connected");
    /* Reset counter */
    timeOut = 0;
    /* Stay if counter is <=5 */
    while (timeOut <= BLEtries) {

      t = sht.getTemperature();
      h = sht.getHumidity();

      static char tt[6];
      dtostrf(t, 5, 1, tt);
      temperatureCharacteristic.setValue(tt);
      temperatureCharacteristic.notify();
      Serial.print(tt);
      Serial.print(", ");
      delay(300);
      /* Update Bluetooth characteristics with new values. */
      static char hh[6];
      dtostrf(h, 5, 1, hh);
      humidityCharacteristic.setValue(hh);
      humidityCharacteristic.notify();
      Serial.print(hh);
      Serial.println(" ");
      timeOut++;
    }
    BLEDevice::getAdvertising()->stop();
  }
}

Code for the client:

#include <BLEDevice.h>

static BLEUUID serviceUUID("91bad492-b950-4226-aa2b-4ede9fa42f59");
static BLEUUID charUUID("cba1d466-344c-4be3-ab3f-189f80dd7518");

uint32_t count = 0;
static boolean doConnect = false;
static boolean doScan = false;
static BLEAdvertisedDevice *myDevice;

char *OUTtemp = 0;
boolean newTemperature = false;

static BLEClient *pClient;

/* Activate notify */
const uint8_t notificationOn[] = { 0x1, 0x0 };
const uint8_t notificationOff[] = { 0x0, 0x0 };

//When the BLE Server sends a new temperature reading with the notify property
static void temperatureNotifyCallback(BLERemoteCharacteristic *pBLERemoteCharacteristic,
                                      uint8_t *pData, size_t length, bool isNotify) {
  //store temperature value
  OUTtemp = (char *)pData;
  newTemperature = true;
}

bool connectToServer() {
  if (pClient != nullptr) {
    delete pClient;
  }
  pClient = BLEDevice::createClient();
  if (!pClient->connect(myDevice)) {
    Serial.print("Failed to find our service UUID: ");
    Serial.println(serviceUUID.toString().c_str());
    return false;
  }

  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;
  }

  BLERemoteCharacteristic *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;
  }

  if (pRemoteCharacteristic->canNotify()) {
    Serial.print(" characteristic can notify UUID: ");
    Serial.println(charUUID.toString().c_str());
pRemoteCharacteristic->getDescriptor(BLEUUID((uint16_t)0x2902))->writeValue((uint8_t *)notificationOn, 2, true);
pRemoteCharacteristic->registerForNotify(temperatureNotifyCallback); 
}

  pClient->disconnect();
  return true;
}

class MyAdvertisedDeviceCallbacks : public BLEAdvertisedDeviceCallbacks {
  void onResult(BLEAdvertisedDevice advertisedDevice) {
    doScan = true;
    if (advertisedDevice.haveServiceUUID() && advertisedDevice.isAdvertisingService(serviceUUID)) {
      BLEDevice::getScan()->stop();
      if (myDevice != nullptr) {
        delete myDevice;
      }
      myDevice = new BLEAdvertisedDevice(advertisedDevice);
      doConnect = true;
      doScan = false;
    }
  }
};

void setup() {
  Serial.begin(115200);
  Serial.println("Starting Arduino BLE Client application...");
  BLEDevice::init("");
  BLEScan *pBLEScan = BLEDevice::getScan();
  pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks(), true);
  //pBLEScan->setInterval(1000);
  //pBLEScan->setWindow(1000);
  pBLEScan->setActiveScan(true);
  pBLEScan->start(10, false);
}

void loop() {
  if (doConnect) {
    count++;
    //Serial.printf("Count: %d\n", count);
    //Serial.printf("Free Heap Size: %d\n", esp_get_free_heap_size());
    //Serial.printf("System Free Heap Size: %d\n", system_get_free_heap_size());
    //Serial.printf("Minimum Free Heap Size: %d\n", esp_get_minimum_free_heap_size());

    connectToServer();
    Serial.println("Temperature:");
    Serial.print(OUTtemp);
    Serial.println("C");

    doConnect = false;
    doScan = true;
  } else if (doScan) {
    doScan = false;
    BLEDevice::getScan()->start(10, false);
  }

  delay(1000);
}

Anyone can give me some indications to find the problem?

Thanks,
Matteo

There are a lot of Serial.print s in your code, can you highlight which section does not print the information you want.

And when you say

do you not see any information at all? what output are you printing to?

I can't print the variable into which I write the temperature sent over BLE on the client side, here:

Serial.print(OUTtemp);

The variable prints empty despite I set its global value = 0:

char* OUTtemp = 0;

So, I guess there is a (failed) attempt by the client to write its value.

I have a project that receives commands from an iphone using ESP32 BLE. At first it did not work but then I soon discovered that I could not retrieve BLE data straight into a variable in my sketch without referring to the referencing class. I am not a programming expert but this worked for me.

I declared a variable in the sketch like this ;
std::string KrxValue;

I then passed the BLE data into KrxValue directly from the class.

class MyCallbacks: public BLECharacteristicCallbacks {
    void onWrite(BLECharacteristic *pCharacteristic) {

      if (rxValue.length() > 0) { 
        Serial.println("*********");
        Serial.print("Received Value: ");
        for (int i = 0; i < rxValue.length(); i++)
          Serial.print(rxValue[i]);
       KrxValue = pCharacteristic->getValue();

        Serial.println();
        Serial.println("*********");
      }
    }   
};

And that allowed me to reference the BLE value in my loop function

if (!KrxValue.empty()){

    command = KrxValue.c_str(); 
    
   Serial.print("KValue: ");
   Serial.println(command);
}

Also, I cannot see where you writeValue on your client side script to send the value to the server, you are just setting the value to a local variable called OUTtemp.

I suggest you look at the ESP32 BLE Arduino examples server/client. Get a basic example working and then build on your code once you can recieve values.

Here I write the value of the characteristic:

For clarity: the code I posted above started with the basic BLE library example that worked perfectly fine. I have complicated the example to make it tied to my needs (portable device) till when I met with data loss issues that did not allow me to use that approach any more. I've spent several hours on that issue. I then changed approach and built the new scripts above that work with connect/disconnect and deep sleep mode without crashes and data leaks.

For your previous suggestion, it may be useful and I will try it tonight.

My bad, my sketch window was not expanded :grinning:

I recommended using version control. Whenever, I make changes to a project I make a note of the changes I made and the date the changes were made. Add comments in the sketch of a separate file. Whenever, I make big changes I create a new file i.e. "projectfilename_v1_3" , that way, if something stops working I can easily go back to a working version and I know what changes were made before things stopped working.

in the sketch or a separate file

The problem is that the "new approach" with the pClient->disconnect(); was never able to write the notified characteristics (while it is able to connect to the peripheral and see the characteristics, as explained above).

I have the feeling that the issue is somewhat related to the fact that I use

in the same function as:

pRemoteCharacteristic->registerForNotify(temperatureNotifyCallback);

However, I do not know how to move the getDescriptor part in the main loop as pRemoteCharacteristic is not a global variable and thus can't be moved out from the function where is created.

If you look at the BLE_Client example, it sets the UUID once and then writes the value in the loop function pRemoteCharacteristic->writeValue(newValue.c_str(), newValue.length());

But you set the UUID and set it again when writing the value.
why don't you try

pRemoteCharacteristic->writeValue( valuetowrite);

Again, I state that I am not an expert but if the example code works then I don't see why you need to add extra parameters.

Also, in the BLE_Client example when it writes to the server it try's to reconnect to the server if the connection drops.

else if(doScan){
    BLEDevice::getScan()->start(0);  
  }
1 Like

Today I had time to play with my code and @arduinobotting suggestions did the magic! My wireless low-power BLE peripheral/client is now working smoothly. here the fixed code for the client:

#include <BLEDevice.h>
#include <ctype.h>

static BLEUUID serviceUUID("91bad492-b950-4226-aa2b-4ede9fa42f59");
static BLEUUID charUUID("cba1d466-344c-4be3-ab3f-189f80dd7518");

static BLEUUID tempUUID("cba1d466-344c-4be3-ab3f-189f80dd7518");
static BLEUUID humiUUID("ca73b3ba-39f6-4ab3-91ae-186dc9577d99");

static boolean doConnect = false;
static boolean doScan = false;
static BLEAdvertisedDevice *myDevice;

static BLEClient *pClient;
char *OUTtemp = 0;
char *OUThumi = 0;
std::string valuet;
std::string valueh;

//When the BLE Server sends a new temperature reading with the notify property
static void temperatureNotifyCallback(BLERemoteCharacteristic *pBLERemoteCharacteristic,
                                      uint8_t *pData, size_t length, bool isNotify) {
}

bool connectToServer() {
  if (pClient != nullptr) {
    delete pClient;
  }

  pClient = BLEDevice::createClient();
  if (!pClient->connect(myDevice)) {
    Serial.print("Failed to find our service UUID: ");
    Serial.println(serviceUUID.toString().c_str());
    pClient->setMTU(517);  //set client to request maximum MTU from server (default is 23 otherwise)
    return false;
  }

  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;
  }

  BLERemoteCharacteristic *temperatureCharacteristic = pRemoteService->getCharacteristic(tempUUID);

  if (temperatureCharacteristic == nullptr) {
    Serial.println("-  failed to find our characteristic UUID: ");
    Serial.println(tempUUID.toString().c_str());
    pClient->disconnect();
    return false;
  }

  if (temperatureCharacteristic->canRead()) {
    Serial.println(" -  characteristic can Read");
    valuet = temperatureCharacteristic->readValue();

    OUTtemp = new char[valuet.length() + 1];
    strcpy(OUTtemp, valuet.c_str());
  }
  temperatureCharacteristic->registerForNotify(temperatureNotifyCallback);

  pClient->disconnect();
  return true;
}

class MyAdvertisedDeviceCallbacks : public BLEAdvertisedDeviceCallbacks {
  void onResult(BLEAdvertisedDevice advertisedDevice) {
    doScan = true;
    if (advertisedDevice.haveServiceUUID() & advertisedDevice.isAdvertisingService(serviceUUID)) {
      BLEDevice::getScan()->stop();
      if (myDevice != nullptr) {
        delete myDevice;
      }
      myDevice = new BLEAdvertisedDevice(advertisedDevice);
      doConnect = true;
      doScan = false;
    }
  }
};

void setup() {
  Serial.begin(115200);
  Serial.println("Starting Arduino BLE Client application...");
  BLEDevice::init("Central station");
  esp_err_t errRc = esp_ble_tx_power_set(ESP_BLE_PWR_TYPE_DEFAULT, ESP_PWR_LVL_P9);
  esp_ble_tx_power_set(ESP_BLE_PWR_TYPE_ADV, ESP_PWR_LVL_P9);
  esp_ble_tx_power_set(ESP_BLE_PWR_TYPE_SCAN, ESP_PWR_LVL_P9);
  BLEScan *pBLEScan = BLEDevice::getScan();
  pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks(), false);
  pBLEScan->setActiveScan(true);
  pBLEScan->start(1, false);
}

void loop() {
  if (doConnect) {
    connectToServer();
    Serial.println("Temperature:");
    Serial.print(OUTtemp);
    Serial.println("C");
    doConnect = false;
    doScan = true;
  } else if (doScan) {
    doScan = false;
    BLEDevice::getScan()->start(1, false);
  }
}

Thanks!
Matteo

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