espressif/arduino-esp32

BLE Arduino library - Characteristic with multiple descriptors with the same UUID not supported

Closed this issue · 7 comments

Hardware:

Board: Sparkfun ESP32 Thing
Core Installation/update date: No different as of today (28/1/2018) from origin/master of https://github.com/espressif/arduino-esp32.git
IDE name: Arduino IDE 1.8.5
Flash Frequency: 80Mhz
Upload Speed: 921600

Description:

Side Note: In my opinion, the BLE library for ESP32 with the Arduino IDE is an excellent piece of work. The API is well-structured and clearly written from from the ground up, uses meaningful classes, consistent naming, etc. etc. I have found it and presumably therefore the underlying esp-idf code to be robust.

I have encountered what appears to be an incorrect (and hopefully unnecessary) constraint in the implementation of the BLECharacteristic::addDescriptor method. If multiple descriptors with the same UUID are added to a characteristic, only the first one gets a valid handle allocated during the BLEService start() method and, in my tests, is the only one seen by a BLE GATT client using service discovery.

As I understand the BLE spec's, a characteristic's descriptors are not required to have distinct UUIDs and it is intended that in some cases they do not. For example, if Characteristic Presentation Format descriptors (UUID 0x2904) are being used, there are multiple of them for a characteristic with multiple data elements in its payload. (The Characteristic Aggregate Format descriptor (0x2905) can then be used to define the order of multiple Characteristic Presentation Format descriptors within that payload.)

In this library, the association of descriptors with a characteristic is implemented using a std::map and addDescriptor uses setByUUID method to update that map with a UUID as the key. Since std::map requires keys to be unique, that would appear to be at least one cause of this issue. I do not know whether the underlying software or firmware also imposes such a constraint and hence do not know whether it can be fixed in this library alone.

Can anyone with knowledge of the underlying BLE software/firmware comment on whether characteristics with duplicate descriptor UUIDs are supported further down the stack?

Sketch:

//Change the code below by your sketch
/*
    Test sketch to demonstrate isse with multiple descriptors with the same UUID not working with the ESP32 BLE Arduino library.
    Based on examples/BLE_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
*/

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

#define SERVICE_UUID        "4fafc201-1fb5-459e-8fcc-c5c9c331914b"
#define CHARACTERISTIC_UUID "beb5483e-36e1-4688-b7f5-ea07361b26a8"

// Logically, our characteristic value consists of two fields, the first an unsigned 8 bit integer, the second an unsigned 16 bit integer.
// Physically, that is just three octets.
uint8_t characteristicValue1[] = {
  0x11,
  0x22, 0x33,
};

// The logical structure of the above is described by two Characteristic Presentation Format descriptors, one for each field. See:
// https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.descriptor.gatt.characteristic_presentation_format.xml
// With more than one Characteristic Presentation Format descriptors, we should also have a Characteristic Aggregate Format descriptor,
// however that would need the handles of the Characteristic Presentation Format descriptors but we don't get a valid handle for the second one.
uint8_t presentationFormat1[] = {
  0x04, // Format = 4 = "unsigned 8-bit integer"
  0x00, // Exponent = 0
  0x00, // Unit = 0x2700 = "unitless" (low byte)
  0x27, // ditto (high byte)
  0x01, // Namespace = 1 = "Bluetooth SIG Assigned Numbers"
  0x00, // Description = 0 = "unknown" (low byte)
  0x00, // ditto (high byte)
};

uint8_t presentationFormat2[] = {
  0x04, // Format = 6 = "unsigned 16-bit integer"
  0x00, // Exponent = 0
  0x00, // Unit = 0x2700 = "unitless" (low byte)
  0x27, // ditto (high byte)
  0x01, // Namespace = 1 = "Bluetooth SIG Assigned Numbers"
  0x00, // Description = 0 = "unknown" (low byte)
  0x00, // ditto (high byte)
};

void setup() {
  Serial.begin(115200);
  delay(1000);
  Serial.println("\r\nStarting BLE work!");

  BLEDevice::init("MyESP32");
  BLEServer *pServer = BLEDevice::createServer();
  BLEService *pService = pServer->createService(SERVICE_UUID);
  BLECharacteristic *pCharacteristic = pService->createCharacteristic(
                                         CHARACTERISTIC_UUID,
                                         BLECharacteristic::PROPERTY_READ |
                                         BLECharacteristic::PROPERTY_WRITE
                                       );

  // create two descriptors with the same UUID                                     
  BLEDescriptor *pDescriptor1 = new BLEDescriptor((uint16_t)0x2904); // Characteristic Presentation Format
  BLEDescriptor *pDescriptor2 = new BLEDescriptor((uint16_t)0x2904); // ditto
  // and one with a different UUID
  BLEDescriptor *pDescriptor3 = new BLEDescriptor((uint16_t)0x2901); // Characteristic User Description
  // confirm that the second descriptor was created despite its duplicate UUID
  if (pDescriptor2)
    Serial.println("pDescriptor2 created OK");
  else
    Serial.println("!pDescriptor2");
  // attach all three descriptors to our one characteristic
  pCharacteristic->addDescriptor(pDescriptor1);
  pCharacteristic->addDescriptor(pDescriptor2);
  pCharacteristic->addDescriptor(pDescriptor3);

  // give the characteristic a value and start the service
  pCharacteristic->setValue(characteristicValue1, sizeof characteristicValue1);
  pService->start();

  // set the descriptors' values
  pDescriptor1->setValue(presentationFormat1, sizeof presentationFormat1);
  pDescriptor2->setValue(presentationFormat2, sizeof presentationFormat2);
  pDescriptor3->setValue("My Test Characteristic");

  // enable advertising so that this device can be easily found
  BLEAdvertising *pAdvertising = pServer->getAdvertising();
  pAdvertising->start();

  // have valid handles been allocated for the descriptors?
  Serial.print("pDescriptor1->getHandle()=0x"); Serial.println(pDescriptor1->getHandle(), HEX);
  Serial.print("pDescriptor2->getHandle()=0x"); Serial.println(pDescriptor2->getHandle(), HEX);
  Serial.print("pDescriptor3->getHandle()=0x"); Serial.println(pDescriptor3->getHandle(), HEX);

  Serial.println("Look for MyESP32 and do service discovery with your favourite BLE scanner.");
  Serial.println("I used \"BLE Scanner 4.0\" (Version 2.0.1) from \"bluepixel technologies\" on iOS");
}

void loop() {
  // put your main code here, to run repeatedly:
  delay(2000);
}

Debug Messages:

pDescriptor1->getHandle()=0x2C
pDescriptor2->getHandle()=0xFFFF
pDescriptor3->getHandle()=0x2B

After creating this issue I realised that my repo was not actually up to date, so I updated it (28/1/2018 from https://github.com/espressif/arduino-esp32.git and the BLE library from https://github.com/nkolban/ESP32_BLE_Arduino.git) then retested the above sketch. Results were the same.

I note that the BLE library has had some specific support for Presentation Format (0x2904) descriptors added recently, however this appears to be all about the maintenance of the Presentation Format payload and does not affect this issue.

Hey, @timr49 . My question might be unrelated but will still ask. What is the correct presentation format for utf-8? I am trying;

'''
uint8_t presentationFormat2[] = {
0x19, // Format = 25 = UTF-8 String
0x00, // Exponent = 0
0x00, // Unit = 0x2700 = "unitless" (low byte)
0x27, // ditto (high byte)
0x01, // Namespace = 1 = "Bluetooth SIG Assigned Numbers"
0x00, // Description = 0 = "unknown" (low byte)
0x00, // ditto (high byte)
};
'''
But LightBlue Explorer, nRF Connect and BlueCap applications still does not show the characteristic in string format by default. I have to choose String format if app allows such custom format for the characteristic. Do you have any idea?

Hi @MehmetCagriK,

Unless you know that an app is designed to accept and process a Presentation Format (e.g. because the documentation says it does), I would assume that it does not. For example, there may be nothing wrong with your presentation format, it is just that those (and other) apps do not expect presentation formats and do not do any with them if they get them. Rather, an app knows how to present the data based on other information (e.g. UUIDs plus hardwired code in the app). Diagnostic apps that I have used (e.g. some of those you mention) either present the data two ways (raw and string) or leave it to the user to select.

My guess is that no-one bothers to implement presentation forms because (a) as specified they are not sufficient (e.g. they do not tell the byte order of multibyte quantities); and (b) the client knows the format anyway so why bother writing the code to dynamically format the data based on presentation format when you know the format anyway?

More generally, if you are making an effort to create and send presentation formats from your server because you expect someone else's client to use them in a meaningful way, you may well be wasting your time unless you already have the details of what the client will do. If you are developing the client, then you don't need the presentation format because you know the format already.

I would be happy to be proven wrong - can anyone identify clients or servers that use presentation formats seriously?

reopened. closed in error.

My assumption was that these are three different apps developed by different companies that would bother using presentation format. But it is no big deal, it just bothers me that everytime i get out of characteris's specific screen, it just defaults to hex format. Do you know an app that at least remembers my choices for different characteristics display format so at least I can set it once, not everytime?

stale commented

This issue has been automatically marked as stale because it has not had recent activity. It will be closed in 14 days if no further activity occurs. Thank you for your contributions.

stale commented

This stale issue has been automatically closed. Thank you for your contributions.