nkolban / esp32-snippets

Sample ESP32 snippets and code fragments
https://leanpub.com/kolban-ESP32
Apache License 2.0
2.35k stars 710 forks source link

BLE_client reconnect hangs #1006

Open kerimt-dek opened 4 years ago

kerimt-dek commented 4 years ago

Hello,

I have a problem with my ESP32 central application which drives me nuts. This application needs to connect to server occasionally and write single characteristic data, then it disconnects. Problem is that it crashes at subsequent connect and writes. I modified BLE_client.ino in order to reproduce the problem. Original BLE_client.ino works fine but what I changed is to disconnect between writes. I also scan between writes just to be sure that server is disconnected, alive and advertising. It appears correctly in each scan cycle.

It fails in different ways, sometimes as: 16:54:07.545 -> - Created client 16:54:08.101 -> - Connected to server 16:54:10.154 -> - Found our service 16:54:10.154 -> Failed to find our characteristic UUID: 0000fcd9-0000-1000-8000-00805f9b34fb

and sometimes as: 16:51:25.455 -> - Created client 16:51:25.938 -> - Connected to server 16:51:28.245 -> abort() was called at PC 0x400851ad on core 0

or just freezes like this: 16:58:20.496 -> - Created client 16:58:21.419 -> - Connected to server

I tried different ESP32 boards, same thing happens. First connection followed by write always works, but eventually it fails at second or third or ... attempt. I have found a way around it by simply issuing soft reset after each connect-write-disconnect sequence, which solves the problem. But having something like this and unexplained is not a good practice.

Can someone please have a look and let me know what am I doing wrong. Thanks


/**
   A BLE client example that is rich in capabilities.
*/

#include "BLEDevice.h"
//#include "BLEScan.h"

// The remote service we wish to connect to.
static BLEUUID serviceUUID("0000fcc0-0000-1000-8000-00805f9b34fb");
// The characteristic of the remote service we are interested in.
//static BLEUUID charUUID("FCD9");
static BLEUUID charUUID("0000fcd9-0000-1000-8000-00805f9b34fb");

static BLEAddress *pServerAddress;
static boolean doConnect = false;
static boolean connected = false;
static BLERemoteCharacteristic* pRemoteCharacteristic;

uint8_t TURN_VALVE_ON[5]  = {0x69, 0x03, 0x01, 0x00, 0x05};
uint8_t TURN_VALVE_OFF[5] = {0x69, 0x03, 0x00, 0x00, 0x05};

BLEClient*  pClient  = BLEDevice::createClient();
BLEScan* pBLEScan;

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

bool connectToServer(BLEAddress pAddress) {
  Serial.print("Forming a connection to ");
  Serial.println(pAddress.toString().c_str());

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

  // Connect to the remove BLE Server.
  pClient->connect(pAddress);
  Serial.println(" - Connected to server");

  // 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());
    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());
    return false;
  }
  Serial.println(" - Found our characteristic");

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

  pRemoteCharacteristic->registerForNotify(notifyCallback);

  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.getServiceUUID().equals(serviceUUID)) {

        //
        Serial.print("Found our device!  address: ");
        //advertisedDevice.getScan()->stop();

        pServerAddress = new BLEAddress(advertisedDevice.getAddress());
        doConnect = 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 30 seconds.
  //BLEScan* pBLEScan = BLEDevice::getScan();
  pBLEScan = BLEDevice::getScan();
  pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks());
  pBLEScan->setActiveScan(true);
  //pBLEScan->start(30);
  //pBLEScan->clearResults();  // release memory 
} // 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.

  pBLEScan->start(30);
  pBLEScan->clearResults();  // release memory 
  if (connectToServer(*pServerAddress)) {
    Serial.println("We are now connected to the BLE Server, turn ON.");
    connected = true;
    pRemoteCharacteristic->writeValue(TURN_VALVE_ON, 5);
    pClient->disconnect();

  } else {
    Serial.println("We have failed to connect to the server; there is nothin more we will do.");
  }

  delay(10000); // Delay a second between loops.

  pBLEScan->start(30);
  pBLEScan->clearResults();  // release memory
  if (connectToServer(*pServerAddress)) {
    Serial.println("We are now connected to the BLE Server, turn OFF.");
    connected = true;
    pRemoteCharacteristic->writeValue(TURN_VALVE_OFF, 5);
    pClient->disconnect();

  } else {
    Serial.println("We have failed to connect to the server; there is nothin more we will do.");
  }

  delay(10000); // Delay a second between loops.

} // End of loop
chegewara commented 4 years ago

Hard to say, but i would start with checking memory.

kerimt-dek commented 4 years ago

Thank you for quick replay and good advice.

Before each scan I inserted this

freeMem = ESP.getFreeHeap(); Serial.print("Remaining memory "); Serial.println(freeMem); then i get 19:07:13.993 -> Remaining memory 187560 19:08:01.581 -> Remaining memory 150576 19:08:45.592 -> Remaining memory 113728

and then it froze like this: 19:09:15.589 -> - Created client 19:09:15.954 -> - Connected to server

It's obviously leaking isn't it? Interestingly, I had to press reset button few times to get this far because it usually fails after first write.

What else can I do?

chegewara commented 4 years ago

I dont know which version you are using, the one that should delete BLEClient after disconnect, or the one where you need to do it, so my advice is to try to delete(pClient) after disconnect. If it crash then you dont need it.

EDIT you may need to add more cleanup code, like delete services and/or characteristics too

kerimt-dek commented 4 years ago

Just tried delete(pClient) after disconnect, crashed

19:50:44.703 -> - Created client 19:50:45.065 -> - Connected to server 19:50:48.126 -> - Found our service 19:50:48.164 -> - Found our characteristic 19:50:48.200 -> The characteristic value was: i 19:50:48.200 -> We are now connected to the BLE Server, turn ON. 19:50:48.237 -> CORRUPT HEAP: Bad head at 0x3ffe1d50. Expected 0xabba1234 got 0x3ffe3f04

Can you please let me know how to do the cleanup, I'm not very experienced with ESP32

kerimt-dek commented 4 years ago

I tried delete(pRemoteCharacteristic); after disconnect, it didn't help.

But one of the things I changed in original example is to declare

BLEClient pClient = BLEDevice::createClient(); BLEScan pBLEScan;

as global variables. Could this be the problem?

kerimt-dek commented 4 years ago

I think I fixed it. Problem is that new client should not be created every time I am connecting to this device. I declare global variable static BLEClient *pClient = NULL; and when creating client if (pClient == NULL) pClient = BLEDevice::createClient();

But my memory is still decreasing after each connect-disconnect cycle, but very slightly. Should I be concerned? 23:44:07.140 -> Remaining memory 188216 23:44:49.466 -> Remaining memory 151224 23:45:31.810 -> Remaining memory 151196 23:46:14.076 -> Remaining memory 151172 23:46:56.920 -> Remaining memory 151148 23:47:39.517 -> Remaining memory 151116

chegewara commented 4 years ago

Actually you should create new BLEClient every time you connect, but if it works to you then ok.

It seems to be about 30 bytes leak. Eventually probably it will be a problem.

kerimt-dek commented 4 years ago

But if I do that memory is decreasing by 37000B every time I connect? This way it's decreasing that much only after first connect. I got the clue here: https://github.com/nkolban/esp32-snippets/issues/854

You shouldn't create a new client every time you reconnect, you should make pClient a global and call createcClient() once in setup(). Also you may want to test if pClient->connect() returns true and set the connected flag after that call because occasionally the onConnect call back is called even when the connection fails as disconnect event gets sent before the open event that triggers the callback.

kerimt-dek commented 4 years ago

How can I find what causes this small memory leak, any idea?

chegewara commented 4 years ago

Nope, sorry. This may be just few pointers, like registered notify callback, so maybe try to unregister it before disconnect.

kerimt-dek commented 4 years ago

Thanks I will have a look at that. By the way, I think BLE_client example should be updated because it doesn't represent realistic scenario. And client, or BLE-Wifi gateway, is most common use case for ESP32, simply because power consumption is too high for battery operated BLE sensors. Server device is never connected at all times and sometimes even device may issue disconnect, or simply signal gets lost and timeout kicks in. I looked up this issue and found several references where reconnect failure is reported - probably for similar reasons. I think it would be great benefit for newbies like myself.

kerimt-dek commented 4 years ago

Now I got it working. Small memory leak was repaired by


    pClient->disconnect();
    delete(pServerAddress);
kerimt-dek commented 4 years ago

I finally have it working reliably and with no memory leak. It works also if I remove battery from server before scan, between device scanned and connect attempt - does't hang. But I had to comment this line //pClient->setClientCallbacks(new MyClientCallback()); // ??? because it causes memory leak and I don't know how to fix it.

#include "BLEDevice.h"

// The remote service we wish to connect to.
static BLEUUID serviceUUID("0000fcc0-0000-1000-8000-00805f9b34fb");
// The characteristic of the remote service we are interested in.
//static BLEUUID charUUID("FCD9");
static BLEUUID charUUID("0000fcd9-0000-1000-8000-00805f9b34fb");

static BLEAddress *pServerAddress;
static boolean doConnect = false;
static boolean connected = false;
static BLERemoteCharacteristic* pRemoteCharacteristic;

uint8_t TURN_VALVE_ON[5]  = {0x69, 0x03, 0x01, 0x00, 0x05};
uint8_t TURN_VALVE_OFF[5] = {0x69, 0x03, 0x00, 0x00, 0x05};

//BLEClient*  pClient = BLEDevice::createClient();

static BLEScan* pBLEScan;
static BLEClient *pClient = NULL;

bool on_off = false;
long int freeMem;

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

class MyClientCallback : public BLEClientCallbacks
{
    void onConnect(BLEClient *pclient)
    {
      connected = true;
      //Serial.println("onConnect");
    }

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

bool connectToServer(BLEAddress pAddress) {
  Serial.print("Forming a connection to ");
  Serial.println(pAddress.toString().c_str());

  if (pClient == NULL) pClient  = BLEDevice::createClient();
  Serial.println(" - Created client");
  //pClient->setClientCallbacks(new MyClientCallback()); // ???

  if (pClient->connect(pAddress))
    Serial.println(" - Connected to server");
  else {
    Serial.println(" - Server not there");
    return false;
  }

  // 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());
    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());
    return false;
  }
  Serial.println(" - Found our characteristic");

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

  pRemoteCharacteristic->registerForNotify(notifyCallback);

  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.getServiceUUID().equals(serviceUUID)) {

        //
        Serial.print("Found our device!  address: ");
        //advertisedDevice.getScan()->stop();

        pServerAddress = new BLEAddress(advertisedDevice.getAddress());
        doConnect = 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 30 seconds.
  //BLEScan* pBLEScan = BLEDevice::getScan();
  //pBLEScan = BLEDevice::getScan();
  //pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks());
  //pBLEScan->setActiveScan(true);

} // End of setup.

// This is the Arduino main loop function.
void loop() {

  freeMem = ESP.getFreeHeap();
  Serial.print("Remaining memory ");
  Serial.println(freeMem);

  on_off = !on_off; // toggle

  Serial.println("Scanning...");
  BLEScan* pBLEScan = BLEDevice::getScan(); //create new scan
  MyAdvertisedDeviceCallbacks myCallbacks;
  pBLEScan->setAdvertisedDeviceCallbacks(&myCallbacks);
  pBLEScan->setActiveScan(true); //active scan uses more power, but get results faster
  BLEScanResults foundDevices = pBLEScan->start(30);
  Serial.print("Devices found: ");
  Serial.println(foundDevices.getCount());
  Serial.println("Scan done!");
  pBLEScan->clearResults();  // release memory

  if (doConnect)  {
    doConnect = false;
    if (connectToServer(*pServerAddress) ) {
      Serial.println("We are now connected to the BLE Server...");
      if (on_off)
        pRemoteCharacteristic->writeValue(TURN_VALVE_ON, 5);
      else
        pRemoteCharacteristic->writeValue(TURN_VALVE_OFF, 5);

      pClient->disconnect();
      delete(pServerAddress);

    } else {
      Serial.println("We have failed to connect to the server; there is nothin more we will do.");
    }

  } else {
    Serial.println("Server not advertising.");

  }
  delay(10000); // Delay a second between loops.

} // End of loop
chegewara commented 4 years ago

//pClient->setClientCallbacks(new MyClientCallback()); // ??? because it causes memory leak and I don't know how to fix it.

Its very simple:

// in setup
MyClientCallback callback = new MyClientCallback();
// then
pClient->setClientCallbacks(callback);