c++bluetooth-lowenergyesp32arduino-c++gatt

Why one of the 2091 BLE Descriptors isn't getting its value on my ESP32 application?


I'm developing a BLE application using a ESP32-WROOM-32.

Everything works great, but there's a strange behavior that I can't address: I have 5 characteristics, 4 are read-only and one is write-only. Every characteristic has a descriptor, whose value indicates the content of the characteristic.

If I assign a descriptor to every characteristic, the write-only one doesn't get its value

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

#define SERVICE_UUID "4fafc201-1fb5-459e-8fcc-c5c9c331914b"
#define CHARACTERISTIC_UUID_1 "7285ab1e-217e-4595-97a1-85975fe801b3"
#define CHARACTERISTIC_UUID_2 "248386d7-cda1-4ffd-a45b-502a7e709ce9"
#define CHARACTERISTIC_UUID_3 "88ac4be2-45e9-4578-9459-124066251e22"
#define CHARACTERISTIC_UUID_4 "596639c8-d4de-4562-9365-7ee0d5409812"
#define CHARACTERISTIC_UUID_5 "945353c0-b3a4-4946-bab7-85da9c8346e3"

BLECharacteristic *pCharacteristic1, *pCharacteristic2, *pCharacteristic3, *pCharacteristic4, *pCharacteristic5;

class ServerCallbacks : public BLEServerCallbacks
{
  void onConnect(BLEServer *pServer)
  {
    Serial.println(F("Connected"));
  };
  void onDisconnect(BLEServer *pServer)
  {
    Serial.println(F("Disconnected"));
    BLEDevice::startAdvertising();
  }
};

class WriteCharacteristicCallback : public BLECharacteristicCallbacks
{
  void onWrite(BLECharacteristic *pCharacteristic, esp_ble_gatts_cb_param_t *param)
  {
    Serial.print(F("Received data: value is "));
    Serial.println((pCharacteristic->getValue()).c_str());
  }
};

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

  Serial.println("\n");
  Serial.println(F("Setup started"));

  BLEDevice::init("TEST Descriptors");

  BLEServer *pServer = BLEDevice::createServer();
  pServer->setCallbacks(new ServerCallbacks());

  BLEService *pService = pServer->createService(SERVICE_UUID);

  pCharacteristic1 = pService->createCharacteristic(
      CHARACTERISTIC_UUID_1,
      BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_NOTIFY);

  pCharacteristic2 = pService->createCharacteristic(
      CHARACTERISTIC_UUID_2,
      BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_NOTIFY);

  pCharacteristic3 = pService->createCharacteristic(
      CHARACTERISTIC_UUID_3,
      BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_NOTIFY);

  pCharacteristic4 = pService->createCharacteristic(
      CHARACTERISTIC_UUID_4,
      BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_NOTIFY);

  pCharacteristic5 = pService->createCharacteristic(
      CHARACTERISTIC_UUID_5,
      BLECharacteristic::PROPERTY_WRITE);

  BLEDescriptor *pCharacteristicDescriptor1 = new BLEDescriptor("2901");
  BLEDescriptor *pCharacteristicDescriptor2 = new BLEDescriptor("2901");
  BLEDescriptor *pCharacteristicDescriptor3 = new BLEDescriptor("2901");
  BLEDescriptor *pCharacteristicDescriptor4 = new BLEDescriptor("2901");
  BLEDescriptor *pCharacteristicDescriptor5 = new BLEDescriptor("2901");

  pCharacteristicDescriptor1->setValue("CHARACTERISTIC 1");
  pCharacteristic1->addDescriptor(pCharacteristicDescriptor1);

  pCharacteristicDescriptor2->setValue("CHARACTERISTIC 2");
  pCharacteristic2->addDescriptor(pCharacteristicDescriptor2);

  pCharacteristicDescriptor3->setValue("CHARACTERISTIC 3");
  pCharacteristic3->addDescriptor(pCharacteristicDescriptor3);

  pCharacteristicDescriptor4->setValue("CHARACTERISTIC 4");
  pCharacteristic4->addDescriptor(pCharacteristicDescriptor4);

  pCharacteristicDescriptor5->setValue("CHARACTERISTIC 5");
  pCharacteristic5->addDescriptor(pCharacteristicDescriptor5);
  pCharacteristic5->setCallbacks(new WriteCharacteristicCallback());

  pService->start();

  BLEAdvertising *pAdvertising = BLEDevice::getAdvertising();
  pAdvertising->addServiceUUID(SERVICE_UUID);
  BLEDevice::startAdvertising();

  Serial.println(F("Setup done"));
}

void loop()
{
  delay(5000);
}

Descriptor not working

If I remove one of the descriptors then everything works

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

#define SERVICE_UUID "4fafc201-1fb5-459e-8fcc-c5c9c331914b"
#define CHARACTERISTIC_UUID_1 "7285ab1e-217e-4595-97a1-85975fe801b3"
#define CHARACTERISTIC_UUID_2 "248386d7-cda1-4ffd-a45b-502a7e709ce9"
#define CHARACTERISTIC_UUID_3 "88ac4be2-45e9-4578-9459-124066251e22"
#define CHARACTERISTIC_UUID_4 "596639c8-d4de-4562-9365-7ee0d5409812"
#define CHARACTERISTIC_UUID_5 "945353c0-b3a4-4946-bab7-85da9c8346e3"

BLECharacteristic *pCharacteristic1, *pCharacteristic2, *pCharacteristic3, *pCharacteristic4, *pCharacteristic5;

class ServerCallbacks : public BLEServerCallbacks
{
  void onConnect(BLEServer *pServer)
  {
    Serial.println(F("Connected"));
  };
  void onDisconnect(BLEServer *pServer)
  {
    Serial.println(F("Disconnected"));
    BLEDevice::startAdvertising();
  }
};

class WriteCharacteristicCallback : public BLECharacteristicCallbacks
{
  void onWrite(BLECharacteristic *pCharacteristic, esp_ble_gatts_cb_param_t *param)
  {
    Serial.print(F("Received data: value is "));
    Serial.println((pCharacteristic->getValue()).c_str());
  }
};

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

  Serial.println("\n");
  Serial.println(F("Setup started"));

  BLEDevice::init("TEST Descriptors");

  BLEServer *pServer = BLEDevice::createServer();
  pServer->setCallbacks(new ServerCallbacks());

  BLEService *pService = pServer->createService(SERVICE_UUID);

  pCharacteristic1 = pService->createCharacteristic(
      CHARACTERISTIC_UUID_1,
      BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_NOTIFY);

  pCharacteristic2 = pService->createCharacteristic(
      CHARACTERISTIC_UUID_2,
      BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_NOTIFY);

  pCharacteristic3 = pService->createCharacteristic(
      CHARACTERISTIC_UUID_3,
      BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_NOTIFY);

  pCharacteristic4 = pService->createCharacteristic(
      CHARACTERISTIC_UUID_4,
      BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_NOTIFY);

  pCharacteristic5 = pService->createCharacteristic(
      CHARACTERISTIC_UUID_5,
      BLECharacteristic::PROPERTY_WRITE);

  BLEDescriptor *pCharacteristicDescriptor1 = new BLEDescriptor("2901");
  BLEDescriptor *pCharacteristicDescriptor2 = new BLEDescriptor("2901");
  BLEDescriptor *pCharacteristicDescriptor3 = new BLEDescriptor("2901");
  BLEDescriptor *pCharacteristicDescriptor4 = new BLEDescriptor("2901");
  BLEDescriptor *pCharacteristicDescriptor5 = new BLEDescriptor("2901");

  pCharacteristicDescriptor1->setValue("CHARACTERISTIC 1");
  pCharacteristic1->addDescriptor(pCharacteristicDescriptor1);

  pCharacteristicDescriptor2->setValue("CHARACTERISTIC 2");
  pCharacteristic2->addDescriptor(pCharacteristicDescriptor2);

  pCharacteristicDescriptor3->setValue("CHARACTERISTIC 3");
  pCharacteristic3->addDescriptor(pCharacteristicDescriptor3);

  pCharacteristicDescriptor4->setValue("CHARACTERISTIC 4");
  // pCharacteristic4->addDescriptor(pCharacteristicDescriptor4);

  pCharacteristicDescriptor5->setValue("CHARACTERISTIC 5");
  pCharacteristic5->addDescriptor(pCharacteristicDescriptor5);
  pCharacteristic5->setCallbacks(new WriteCharacteristicCallback());

  pService->start();

  BLEAdvertising *pAdvertising = BLEDevice::getAdvertising();
  pAdvertising->addServiceUUID(SERVICE_UUID);
  BLEDevice::startAdvertising();

  Serial.println(F("Setup done"));
}

void loop()
{
  delay(5000);
}

enter image description here

If I put the write-only characteristic on a separate service it works

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

#define SERVICE_UUID "4fafc201-1fb5-459e-8fcc-c5c9c331914b"
#define WRITE_SERVICE_UUID "1f425e27-0174-4591-b081-45732e0d93ad"
#define CHARACTERISTIC_UUID_1 "7285ab1e-217e-4595-97a1-85975fe801b3"
#define CHARACTERISTIC_UUID_2 "248386d7-cda1-4ffd-a45b-502a7e709ce9"
#define CHARACTERISTIC_UUID_3 "88ac4be2-45e9-4578-9459-124066251e22"
#define CHARACTERISTIC_UUID_4 "596639c8-d4de-4562-9365-7ee0d5409812"
#define CHARACTERISTIC_UUID_5 "945353c0-b3a4-4946-bab7-85da9c8346e3"

BLECharacteristic *pCharacteristic1, *pCharacteristic2, *pCharacteristic3, *pCharacteristic4, *pCharacteristic5;

class ServerCallbacks : public BLEServerCallbacks
{
  void onConnect(BLEServer *pServer)
  {
    Serial.println(F("Connected"));
  };
  void onDisconnect(BLEServer *pServer)
  {
    Serial.println(F("Disconnected"));
    BLEDevice::startAdvertising();
  }
};

class WriteCharacteristicCallback : public BLECharacteristicCallbacks
{
  void onWrite(BLECharacteristic *pCharacteristic, esp_ble_gatts_cb_param_t *param)
  {
    Serial.print(F("Received data: value is "));
    Serial.println((pCharacteristic->getValue()).c_str());
  }
};

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

  Serial.println("\n");
  Serial.println(F("Setup started"));

  BLEDevice::init("TEST Descriptors");

  BLEServer *pServer = BLEDevice::createServer();
  pServer->setCallbacks(new ServerCallbacks());

  BLEService *pService = pServer->createService(SERVICE_UUID);
  BLEService *pWriteService = pServer->createService(WRITE_SERVICE_UUID);

  pCharacteristic1 = pService->createCharacteristic(
      CHARACTERISTIC_UUID_1,
      BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_NOTIFY);

  pCharacteristic2 = pService->createCharacteristic(
      CHARACTERISTIC_UUID_2,
      BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_NOTIFY);

  pCharacteristic3 = pService->createCharacteristic(
      CHARACTERISTIC_UUID_3,
      BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_NOTIFY);

  pCharacteristic4 = pService->createCharacteristic(
      CHARACTERISTIC_UUID_4,
      BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_NOTIFY);

  pCharacteristic5 = pWriteService->createCharacteristic(
      CHARACTERISTIC_UUID_5,
      BLECharacteristic::PROPERTY_WRITE);

  BLEDescriptor *pCharacteristicDescriptor1 = new BLEDescriptor("2901");
  BLEDescriptor *pCharacteristicDescriptor2 = new BLEDescriptor("2901");
  BLEDescriptor *pCharacteristicDescriptor3 = new BLEDescriptor("2901");
  BLEDescriptor *pCharacteristicDescriptor4 = new BLEDescriptor("2901");
  BLEDescriptor *pCharacteristicDescriptor5 = new BLEDescriptor("2901");

  pCharacteristicDescriptor1->setValue("CHARACTERISTIC 1");
  pCharacteristic1->addDescriptor(pCharacteristicDescriptor1);

  pCharacteristicDescriptor2->setValue("CHARACTERISTIC 2");
  pCharacteristic2->addDescriptor(pCharacteristicDescriptor2);

  pCharacteristicDescriptor3->setValue("CHARACTERISTIC 3");
  pCharacteristic3->addDescriptor(pCharacteristicDescriptor3);

  pCharacteristicDescriptor4->setValue("CHARACTERISTIC 4");
  pCharacteristic4->addDescriptor(pCharacteristicDescriptor4);

  pCharacteristicDescriptor5->setValue("CHARACTERISTIC 5");
  pCharacteristic5->addDescriptor(pCharacteristicDescriptor5);
  pCharacteristic5->setCallbacks(new WriteCharacteristicCallback());

  pService->start();
  pWriteService->start();

  BLEAdvertising *pAdvertising = BLEDevice::getAdvertising();
  pAdvertising->addServiceUUID(SERVICE_UUID);
  pAdvertising->addServiceUUID(WRITE_SERVICE_UUID);
  BLEDevice::startAdvertising();

  Serial.println(F("Setup done"));
}

void loop()
{
  delay(5000);
}

enter image description here enter image description here

Could this mean that I can have a maximum of 4 characteristics with value-descriptors per service? It sounds really weird to me!


Solution

  • I don't know how but I find the problem and the solution, and it is pretty easy and straightforward too.

    The ESP32, by design, handles a maximum of 15 handlers on a BLEService BY DEFAULT. However this number of handlers can be changed, by specifiying it when invoking the constructor (since it has 2 constructors overloaded).

    So instead of doing

    BLEService *pService = pServer->createService(SERVICE_UUID);

    we simply do

    BLEService *pService = pServer->createService(BLEUUID(SERVICE_UUID), 20);

    and tada! Everything works! From the official docs

    A Characteristic uses 2 handlers while a descriptor uses 1

    This means that for every characteristic with a descriptor we need a minimum of 3 handlers. If the descriptor and/or the characteristic contain data (such as values), the number of handlers increases and as soon as the ESP32 saturates its default capability it stops from adding stuff.

    I'm not sure if this is a C/C++ limitation, but a runtime error raised would for sure help to identify the problem.

    Hope that helped!