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);
}
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);
}
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);
}
Could this mean that I can have a maximum of 4 characteristics with value-descriptors per service? It sounds really weird to me!
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!