Преглед на файлове

Merge branch 'bugfix/protocomm_ble_128bit_uuid' into 'master'

protocomm_ble : Fix support for custom service UUIDs

See merge request idf/esp-idf!4978
Angus Gratton преди 6 години
родител
ревизия
6cdfde094d

+ 14 - 3
components/protocomm/include/transports/protocomm_ble.h

@@ -14,6 +14,8 @@
 
 #pragma once
 
+#include <esp_gap_ble_api.h>
+
 #include <protocomm.h>
 
 #ifdef __cplusplus
@@ -22,8 +24,9 @@ extern "C" {
 
 /**
  * BLE device name cannot be larger than this value
+ * 31 bytes (max scan response size) - 1 byte (length) - 1 byte (type) = 29 bytes
  */
-#define MAX_BLE_DEVNAME_LEN 13
+#define MAX_BLE_DEVNAME_LEN   (ESP_BLE_SCAN_RSP_DATA_LEN_MAX - 2)
 
 /**
  * @brief   This structure maps handler required by protocomm layer to
@@ -51,8 +54,16 @@ typedef struct {
      * BLE device name being broadcast at the time of provisioning
      */
     char         device_name[MAX_BLE_DEVNAME_LEN];
-    uint8_t      service_uuid[16];  /*!< SSID of the provisioning service */
-    ssize_t      nu_lookup_count;   /*!< Number of entries in the Name-UUID lookup table */
+
+    /**
+     * 128 bit UUID of the provisioning service
+     */
+    uint8_t      service_uuid[ESP_UUID_LEN_128];
+
+    /**
+     * Number of entries in the Name-UUID lookup table
+     */
+    ssize_t      nu_lookup_count;
 
     /**
      * Pointer to the Name-UUID lookup table

+ 31 - 10
components/protocomm/src/simple_ble/simple_ble.c

@@ -30,25 +30,37 @@ static const char *TAG = "simple_ble";
 static simple_ble_cfg_t *g_ble_cfg_p;
 static uint16_t g_gatt_table_map[SIMPLE_BLE_MAX_GATT_TABLE_SIZE];
 
-uint16_t simple_ble_get_uuid(uint16_t handle)
+static uint8_t adv_config_done;
+#define adv_config_flag      (1 << 0)
+#define scan_rsp_config_flag (1 << 1)
+
+const uint8_t *simple_ble_get_uuid128(uint16_t handle)
 {
-    uint16_t *uuid_ptr;
+    const uint8_t *uuid128_ptr;
 
     for (int i = 0; i < SIMPLE_BLE_MAX_GATT_TABLE_SIZE; i++) {
         if (g_gatt_table_map[i] == handle) {
-            uuid_ptr = (uint16_t *) g_ble_cfg_p->gatt_db[i].att_desc.uuid_p;
-            return *uuid_ptr;
+            uuid128_ptr = (const uint8_t *) g_ble_cfg_p->gatt_db[i].att_desc.uuid_p;
+            return uuid128_ptr;
         }
     }
-    return -1;
+    return NULL;
 }
 
 static void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param)
 {
     switch (event) {
-    case ESP_GAP_BLE_ADV_DATA_SET_COMPLETE_EVT:
-        esp_ble_gap_start_advertising(&g_ble_cfg_p->adv_params);
-
+    case ESP_GAP_BLE_ADV_DATA_RAW_SET_COMPLETE_EVT:
+        adv_config_done &= (~adv_config_flag);
+        if (adv_config_done == 0) {
+            esp_ble_gap_start_advertising(&g_ble_cfg_p->adv_params);
+        }
+        break;
+    case ESP_GAP_BLE_SCAN_RSP_DATA_RAW_SET_COMPLETE_EVT:
+        adv_config_done &= (~scan_rsp_config_flag);
+        if (adv_config_done == 0) {
+            esp_ble_gap_start_advertising(&g_ble_cfg_p->adv_params);
+        }
         break;
     default:
         break;
@@ -87,11 +99,20 @@ static void gatts_profile_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_
             ESP_LOGE(TAG, "set device name failed, error code = 0x%x", ret);
             return;
         }
-        ret = esp_ble_gap_config_adv_data(&g_ble_cfg_p->adv_data);
+        ret = esp_ble_gap_config_adv_data_raw(g_ble_cfg_p->raw_adv_data_p,
+                                              g_ble_cfg_p->raw_adv_data_len);
+        if (ret) {
+            ESP_LOGE(TAG, "config raw adv data failed, error code = 0x%x ", ret);
+            return;
+        }
+        adv_config_done |= adv_config_flag;
+        ret = esp_ble_gap_config_scan_rsp_data_raw(g_ble_cfg_p->raw_scan_rsp_data_p,
+                                                   g_ble_cfg_p->raw_scan_rsp_data_len);
         if (ret) {
-            ESP_LOGE(TAG, "config adv data failed, error code = 0x%x", ret);
+            ESP_LOGE(TAG, "config raw scan rsp data failed, error code = 0x%x", ret);
             return;
         }
+        adv_config_done |= scan_rsp_config_flag;
         break;
     case ESP_GATTS_READ_EVT:
         g_ble_cfg_p->read_fn(event, gatts_if, param);

+ 12 - 6
components/protocomm/src/simple_ble/simple_ble.h

@@ -32,11 +32,16 @@ typedef void (simple_ble_cb_t)(esp_gatts_cb_event_t event, esp_gatt_if_t p_gatts
 typedef struct {
     /** Name to be displayed to devices scanning for ESP32 */
     const char *device_name;
-    /** Advertising data content, according to "Supplement to the Bluetooth Core Specification" */
-    esp_ble_adv_data_t adv_data;
+    /** Raw advertisement data */
+    uint8_t *raw_adv_data_p;
+    uint8_t raw_adv_data_len;
+    /** Raw scan response data */
+    uint8_t *raw_scan_rsp_data_p;
+    uint8_t raw_scan_rsp_data_len;
     /** Parameters to configure the nature of advertising */
     esp_ble_adv_params_t adv_params;
-    /** Descriptor table which consists the configuration required by services and characteristics */
+    /** Descriptor table which consists of the configuration
+     * required by services and characteristics */
     esp_gatts_attr_db_t *gatt_db;
     /** Number of entries in the gatt_db descriptor table */
     ssize_t gatt_db_count;
@@ -94,14 +99,15 @@ esp_err_t simple_ble_start(simple_ble_cfg_t *cfg);
  */
 esp_err_t simple_ble_stop();
 
-/** Convert handle to UUID of characteristic
+/** Convert handle to 128 bit UUID of characteristic
  *
  * This function can be easily used to get the corresponding
  * UUID for a characteristic that has been created, and the one for
  * which we only have the handle for.
  *
- * @return uuid the UUID of the handle, -1 in case of invalid handle
+ * @return Pointer to UUID of the characteristic
+ *         NULL in case of invalid handle
  */
-uint16_t simple_ble_get_uuid(uint16_t handle);
+const uint8_t *simple_ble_get_uuid128(uint16_t handle);
 
 #endif /* _SIMPLE_BLE_ */

+ 197 - 66
components/protocomm/src/transports/protocomm_ble.c

@@ -15,6 +15,7 @@
 #include <sys/param.h>
 #include <esp_log.h>
 #include <esp_gatt_common_api.h>
+#include <esp_gap_bt_api.h>
 
 #include <protocomm.h>
 #include <protocomm_ble.h>
@@ -28,10 +29,17 @@
 static const char *TAG = "protocomm_ble";
 
 /* BLE specific configuration parameters */
-const uint16_t GATTS_SERVICE_UUID_PROV      = 0xFFFF;
-const uint16_t primary_service_uuid         = ESP_GATT_UUID_PRI_SERVICE;
-const uint16_t character_declaration_uuid   = ESP_GATT_UUID_CHAR_DECLARE;
-const uint8_t char_prop_read_write          = ESP_GATT_CHAR_PROP_BIT_READ | ESP_GATT_CHAR_PROP_BIT_WRITE;
+static const uint16_t primary_service_uuid       = ESP_GATT_UUID_PRI_SERVICE;
+static const uint16_t character_declaration_uuid = ESP_GATT_UUID_CHAR_DECLARE;
+static const uint16_t character_user_description = ESP_GATT_UUID_CHAR_DESCRIPTION;
+static const uint8_t  character_prop_read_write  = ESP_GATT_CHAR_PROP_BIT_READ | ESP_GATT_CHAR_PROP_BIT_WRITE;
+static const uint8_t  ble_advertisement_flags    = ESP_BLE_ADV_FLAG_GEN_DISC | ESP_BLE_ADV_FLAG_BREDR_NOT_SPT;
+
+typedef struct {
+    uint8_t type;
+    uint8_t length;
+    uint8_t *data_p;
+} raw_data_info_t;
 
 typedef struct {
     uint8_t                *prepare_buf;
@@ -41,11 +49,21 @@ typedef struct {
 
 static prepare_type_env_t prepare_write_env;
 
+typedef struct name_uuid128 {
+    const char *name;
+    uint8_t uuid128[ESP_UUID_LEN_128];
+} name_uuid128_t;
+
 typedef struct _protocomm_ble {
     protocomm_t *pc_ble;
-    protocomm_ble_name_uuid_t *g_nu_lookup;
+    name_uuid128_t *g_nu_lookup;
     ssize_t g_nu_lookup_count;
     uint16_t gatt_mtu;
+    uint8_t *service_uuid;
+    uint8_t *raw_adv_data_p;
+    uint8_t raw_adv_data_len;
+    uint8_t *raw_scan_rsp_data_p;
+    uint8_t raw_scan_rsp_data_len;
 } _protocomm_ble_internal_t;
 
 static _protocomm_ble_internal_t *protoble_internal;
@@ -61,34 +79,25 @@ static esp_ble_adv_params_t adv_params = {
 
 static char* protocomm_ble_device_name = NULL;
 
-/* The length of adv data must be less than 31 bytes */
-static esp_ble_adv_data_t adv_data = {
-    .set_scan_rsp        = false,
-    .include_name        = true,
-    .include_txpower     = true,
-    .min_interval        = 0x100,
-    .max_interval        = 0x100,
-    .appearance          = ESP_BLE_APPEARANCE_UNKNOWN,
-    .manufacturer_len    = 0,
-    .p_manufacturer_data = NULL,
-    .service_data_len    = 0,
-    .p_service_data      = NULL,
-    .service_uuid_len    = 0,
-    .p_service_uuid      = NULL,
-    .flag = (ESP_BLE_ADV_FLAG_GEN_DISC | ESP_BLE_ADV_FLAG_BREDR_NOT_SPT),
-};
-
 static void hexdump(const char *msg, uint8_t *buf, int len)
 {
     ESP_LOGD(TAG, "%s:", msg);
     ESP_LOG_BUFFER_HEX_LEVEL(TAG, buf, len, ESP_LOG_DEBUG);
 }
 
+static const uint16_t *uuid128_to_16(const uint8_t *uuid128)
+{
+    return (const uint16_t *) &uuid128[12];
+}
+
 static const char *handle_to_handler(uint16_t handle)
 {
-    uint16_t uuid = simple_ble_get_uuid(handle);
+    const uint8_t *uuid128 = simple_ble_get_uuid128(handle);
+    if (!uuid128) {
+        return NULL;
+    }
     for (int i = 0; i < protoble_internal->g_nu_lookup_count; i++) {
-        if (protoble_internal->g_nu_lookup[i].uuid == uuid ) {
+        if (*uuid128_to_16(protoble_internal->g_nu_lookup[i].uuid128) == *uuid128_to_16(uuid128)) {
             return protoble_internal->g_nu_lookup[i].name;
         }
     }
@@ -226,7 +235,6 @@ static void transport_simple_ble_write(esp_gatts_cb_event_t event, esp_gatt_if_t
     }
 }
 
-
 static void transport_simple_ble_exec_write(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param)
 {
     esp_err_t err;
@@ -314,12 +322,17 @@ static esp_err_t protocomm_ble_remove_endpoint(const char *ep_name)
     return ESP_OK;
 }
 
-
 static ssize_t populate_gatt_db(esp_gatts_attr_db_t **gatt_db_generated)
 {
     int i;
-    /* We need esp_gatts_attr_db_t of size 2 * number of handlers + 1 for service */
-    ssize_t gatt_db_generated_entries = 2 * protoble_internal->g_nu_lookup_count + 1;
+    /* Each endpoint requires 3 attributes:
+     * 1) for Characteristic Declaration
+     * 2) for Characteristic Value (for reading and writing to an endpoint)
+     * 3) for Characteristic User Description (endpoint name)
+     *
+     * Therefore, we need esp_gatts_attr_db_t of size 3 * number of endpoints + 1 for service
+     */
+    ssize_t gatt_db_generated_entries = 3 * protoble_internal->g_nu_lookup_count + 1;
 
     *gatt_db_generated = (esp_gatts_attr_db_t *) malloc(sizeof(esp_gatts_attr_db_t) *
                                                         (gatt_db_generated_entries));
@@ -333,28 +346,38 @@ static ssize_t populate_gatt_db(esp_gatts_attr_db_t **gatt_db_generated)
     (*gatt_db_generated)[0].att_desc.uuid_length        = ESP_UUID_LEN_16;
     (*gatt_db_generated)[0].att_desc.uuid_p             = (uint8_t *) &primary_service_uuid;
     (*gatt_db_generated)[0].att_desc.perm               = ESP_GATT_PERM_READ;
-    (*gatt_db_generated)[0].att_desc.max_length         = sizeof(uint16_t);
-    (*gatt_db_generated)[0].att_desc.length             = sizeof(GATTS_SERVICE_UUID_PROV);
-    (*gatt_db_generated)[0].att_desc.value              = (uint8_t *) &GATTS_SERVICE_UUID_PROV;
+    (*gatt_db_generated)[0].att_desc.max_length         = ESP_UUID_LEN_128;
+    (*gatt_db_generated)[0].att_desc.length             = ESP_UUID_LEN_128;
+    (*gatt_db_generated)[0].att_desc.value              = protoble_internal->service_uuid;
 
     /* Declare characteristics */
     for (i = 1 ; i < gatt_db_generated_entries ; i++) {
-        (*gatt_db_generated)[i].attr_control.auto_rsp   = ESP_GATT_RSP_BY_APP;
-
-        (*gatt_db_generated)[i].att_desc.uuid_length    = ESP_UUID_LEN_16;
-        (*gatt_db_generated)[i].att_desc.perm           = ESP_GATT_PERM_READ |
-                                                          ESP_GATT_PERM_WRITE;
-
-        if (i % 2 == 1) { /* Char Declaration */
-            (*gatt_db_generated)[i].att_desc.uuid_p     = (uint8_t *) &character_declaration_uuid;
-            (*gatt_db_generated)[i].att_desc.max_length = sizeof(uint8_t);
-            (*gatt_db_generated)[i].att_desc.length     = sizeof(uint8_t);
-            (*gatt_db_generated)[i].att_desc.value      = (uint8_t *) &char_prop_read_write;
-        } else { /* Char Value */
-            (*gatt_db_generated)[i].att_desc.uuid_p     = (uint8_t *)&protoble_internal->g_nu_lookup[i / 2 - 1].uuid;
-            (*gatt_db_generated)[i].att_desc.max_length = CHAR_VAL_LEN_MAX;
-            (*gatt_db_generated)[i].att_desc.length     = 0;
-            (*gatt_db_generated)[i].att_desc.value      = NULL;
+        (*gatt_db_generated)[i].attr_control.auto_rsp     = ESP_GATT_RSP_BY_APP;
+
+        if (i % 3 == 1) {
+            /* Characteristic Declaration */
+            (*gatt_db_generated)[i].att_desc.perm         = ESP_GATT_PERM_READ;
+            (*gatt_db_generated)[i].att_desc.uuid_length  = ESP_UUID_LEN_16;
+            (*gatt_db_generated)[i].att_desc.uuid_p       = (uint8_t *) &character_declaration_uuid;
+            (*gatt_db_generated)[i].att_desc.max_length   = sizeof(uint8_t);
+            (*gatt_db_generated)[i].att_desc.length       = sizeof(uint8_t);
+            (*gatt_db_generated)[i].att_desc.value        = (uint8_t *) &character_prop_read_write;
+        } else if (i % 3 == 2) {
+            /* Characteristic Value */
+            (*gatt_db_generated)[i].att_desc.perm         = ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE;
+            (*gatt_db_generated)[i].att_desc.uuid_length  = ESP_UUID_LEN_128;
+            (*gatt_db_generated)[i].att_desc.uuid_p       = protoble_internal->g_nu_lookup[i / 3].uuid128;
+            (*gatt_db_generated)[i].att_desc.max_length   = CHAR_VAL_LEN_MAX;
+            (*gatt_db_generated)[i].att_desc.length       = 0;
+            (*gatt_db_generated)[i].att_desc.value        = NULL;
+        } else {
+            /* Characteristic User Description (for keeping endpoint names) */
+            (*gatt_db_generated)[i].att_desc.perm         = ESP_GATT_PERM_READ;
+            (*gatt_db_generated)[i].att_desc.uuid_length  = ESP_UUID_LEN_16;
+            (*gatt_db_generated)[i].att_desc.uuid_p       = (uint8_t *) &character_user_description;
+            (*gatt_db_generated)[i].att_desc.max_length   = strlen(protoble_internal->g_nu_lookup[i / 3 - 1].name);
+            (*gatt_db_generated)[i].att_desc.length       = (*gatt_db_generated)[i].att_desc.max_length;
+            (*gatt_db_generated)[i].att_desc.value        = (uint8_t *) protoble_internal->g_nu_lookup[i / 3 - 1].name;
         }
     }
     return gatt_db_generated_entries;
@@ -371,6 +394,8 @@ static void protocomm_ble_cleanup(void)
             }
             free(protoble_internal->g_nu_lookup);
         }
+        free(protoble_internal->raw_adv_data_p);
+        free(protoble_internal->raw_scan_rsp_data_p);
         free(protoble_internal);
         protoble_internal = NULL;
     }
@@ -378,11 +403,6 @@ static void protocomm_ble_cleanup(void)
         free(protocomm_ble_device_name);
         protocomm_ble_device_name = NULL;
     }
-    if (adv_data.p_service_uuid) {
-        free(adv_data.p_service_uuid);
-        adv_data.p_service_uuid = NULL;
-        adv_data.service_uuid_len = 0;
-    }
 }
 
 esp_err_t protocomm_ble_start(protocomm_t *pc, const protocomm_ble_config_t *config)
@@ -396,16 +416,6 @@ esp_err_t protocomm_ble_start(protocomm_t *pc, const protocomm_ble_config_t *con
         return ESP_FAIL;
     }
 
-    /* Store service UUID internally */
-    adv_data.service_uuid_len = sizeof(config->service_uuid);
-    adv_data.p_service_uuid   = malloc(sizeof(config->service_uuid));
-    if (adv_data.p_service_uuid == NULL) {
-        ESP_LOGE(TAG, "Error allocating memory for storing service UUID");
-        protocomm_ble_cleanup();
-        return ESP_ERR_NO_MEM;
-    }
-    memcpy(adv_data.p_service_uuid, config->service_uuid, adv_data.service_uuid_len);
-
     /* Store BLE device name internally */
     protocomm_ble_device_name = strdup(config->device_name);
     if (protocomm_ble_device_name == NULL) {
@@ -421,8 +431,8 @@ esp_err_t protocomm_ble_start(protocomm_t *pc, const protocomm_ble_config_t *con
         return ESP_ERR_NO_MEM;
     }
 
-    protoble_internal->g_nu_lookup_count =  config->nu_lookup_count;
-    protoble_internal->g_nu_lookup = malloc(config->nu_lookup_count * sizeof(protocomm_ble_name_uuid_t));
+    protoble_internal->g_nu_lookup_count = config->nu_lookup_count;
+    protoble_internal->g_nu_lookup = malloc(config->nu_lookup_count * sizeof(name_uuid128_t));
     if (protoble_internal->g_nu_lookup == NULL) {
         ESP_LOGE(TAG, "Error allocating internal name UUID table");
         protocomm_ble_cleanup();
@@ -430,7 +440,10 @@ esp_err_t protocomm_ble_start(protocomm_t *pc, const protocomm_ble_config_t *con
     }
 
     for (unsigned i = 0; i < protoble_internal->g_nu_lookup_count; i++) {
-        protoble_internal->g_nu_lookup[i].uuid = config->nu_lookup[i].uuid;
+        memcpy(protoble_internal->g_nu_lookup[i].uuid128, config->service_uuid, ESP_UUID_LEN_128);
+        memcpy((uint8_t *)uuid128_to_16(protoble_internal->g_nu_lookup[i].uuid128),
+               &config->nu_lookup[i].uuid, ESP_UUID_LEN_16);
+
         protoble_internal->g_nu_lookup[i].name = strdup(config->nu_lookup[i].name);
         if (protoble_internal->g_nu_lookup[i].name == NULL) {
             ESP_LOGE(TAG, "Error allocating internal name UUID entry");
@@ -444,6 +457,120 @@ esp_err_t protocomm_ble_start(protocomm_t *pc, const protocomm_ble_config_t *con
     protoble_internal->pc_ble = pc;
     protoble_internal->gatt_mtu = ESP_GATT_DEF_BLE_MTU_SIZE;
 
+    /* The BLE advertisement data (max 31 bytes) consists of:
+     * 1) Flags -
+     *      Size : length (1 byte) + type (1 byte) + value (1 byte) = 3 bytes
+     * 2) Complete 128 bit UUID of the service -
+     *      Size : length (1 byte) + type (1 byte) + value (16 bytes) = 18 bytes
+     *
+     * Remaining 31 - (3 + 18) = 10 bytes could be used for manufacturer data
+     * or something else in the future.
+     */
+    raw_data_info_t adv_data[] = {
+        {   /* Flags */
+            .type   = ESP_BLE_AD_TYPE_FLAG,
+            .length = sizeof(ble_advertisement_flags),
+            .data_p = (uint8_t *) &ble_advertisement_flags
+        },
+        {   /* 128 bit Service UUID */
+            .type   = ESP_BLE_AD_TYPE_128SRV_CMPL,
+            .length = ESP_UUID_LEN_128,
+            .data_p = (uint8_t *) config->service_uuid
+        },
+    };
+
+    /* Get the total raw data length required for above entries */
+    uint8_t adv_data_len = 0;
+    for (uint8_t i = 0; i < (sizeof(adv_data)/sizeof(adv_data[0])); i++) {
+        /* Add extra bytes required per entry, i.e.
+         * length (1 byte) + type (1 byte) = 2 bytes */
+        adv_data_len += adv_data[i].length + 2;
+    }
+    if (adv_data_len > ESP_BLE_ADV_DATA_LEN_MAX) {
+        ESP_LOGE(TAG, "Advertisement data too long = %d bytes", adv_data_len);
+        protocomm_ble_cleanup();
+        return ESP_ERR_NO_MEM;
+    }
+
+    /* Allocate memory for the raw advertisement data */
+    protoble_internal->raw_adv_data_len = adv_data_len;
+    protoble_internal->raw_adv_data_p = malloc(adv_data_len);
+    if (protoble_internal->raw_adv_data_p == NULL) {
+        ESP_LOGE(TAG, "Error allocating memory for raw advertisement data");
+        protocomm_ble_cleanup();
+        return ESP_ERR_NO_MEM;
+    }
+
+    /* Form the raw advertisement data using above entries */
+    for (uint8_t i = 0, len = 0; i < (sizeof(adv_data)/sizeof(adv_data[0])); i++) {
+        protoble_internal->raw_adv_data_p[len++] = adv_data[i].length + 1; // + 1 byte for type
+        protoble_internal->raw_adv_data_p[len++] = adv_data[i].type;
+        memcpy(&protoble_internal->raw_adv_data_p[len],
+               adv_data[i].data_p, adv_data[i].length);
+
+        if (adv_data[i].type == ESP_BLE_AD_TYPE_128SRV_CMPL) {
+            /* Remember where the primary service UUID is kept in the
+             * raw advertisement data, so that it can be used while
+             * populating the GATT database
+             */
+            protoble_internal->service_uuid = &protoble_internal->raw_adv_data_p[len];
+        }
+
+        len += adv_data[i].length;
+    }
+
+    size_t ble_devname_len = strlen(protocomm_ble_device_name);
+    /* The BLE scan response (31 bytes) consists of:
+     * 1) Device name (complete / incomplete) -
+     *      Size : The maximum supported name length
+     *              will be 31 - 2 (length + type) = 29 bytes
+     *
+     * Any remaining space may be used for accommodating
+     * other fields in the future
+     */
+    raw_data_info_t scan_resp_data[] = {
+        {   /* If full device name can fit in the scan response then indicate
+             * that by setting type to "Complete Name", else set it to "Short Name"
+             * so that client can fetch full device name - after connecting - by
+             * reading the device name characteristic under GAP service */
+            .type   = (ble_devname_len > (ESP_BLE_SCAN_RSP_DATA_LEN_MAX - 2) ?
+                       ESP_BLE_AD_TYPE_NAME_SHORT : ESP_BLE_AD_TYPE_NAME_CMPL),
+            .length = MIN(ble_devname_len, (ESP_BLE_SCAN_RSP_DATA_LEN_MAX - 2)),
+            .data_p = (uint8_t *) protocomm_ble_device_name
+        },
+    };
+
+    /* Get the total raw scan response data length required for above entries */
+    uint8_t scan_resp_data_len = 0;
+    for (int i = 0; i < (sizeof(scan_resp_data)/sizeof(scan_resp_data[0])); i++) {
+        /* Add extra bytes required per entry, i.e.
+         * length (1 byte) + type (1 byte) = 2 bytes */
+        scan_resp_data_len += scan_resp_data[i].length + 2;
+    }
+    if (scan_resp_data_len > ESP_BLE_SCAN_RSP_DATA_LEN_MAX) {
+        ESP_LOGE(TAG, "Scan response data too long = %d bytes", scan_resp_data_len);
+        protocomm_ble_cleanup();
+        return ESP_ERR_NO_MEM;
+    }
+
+    /* Allocate memory for the raw scan response data */
+    protoble_internal->raw_scan_rsp_data_len = scan_resp_data_len;
+    protoble_internal->raw_scan_rsp_data_p = malloc(scan_resp_data_len);
+    if (protoble_internal->raw_scan_rsp_data_p == NULL) {
+        ESP_LOGE(TAG, "Error allocating memory for raw response data");
+        protocomm_ble_cleanup();
+        return ESP_ERR_NO_MEM;
+    }
+
+    /* Form the raw scan response data using above entries */
+    for (uint8_t i = 0, len = 0; i < (sizeof(scan_resp_data)/sizeof(scan_resp_data[0])); i++) {
+        protoble_internal->raw_scan_rsp_data_p[len++] = scan_resp_data[i].length + 1; // + 1 byte for type
+        protoble_internal->raw_scan_rsp_data_p[len++] = scan_resp_data[i].type;
+        memcpy(&protoble_internal->raw_scan_rsp_data_p[len],
+               scan_resp_data[i].data_p, scan_resp_data[i].length);
+        len += scan_resp_data[i].length;
+    }
+
     simple_ble_cfg_t *ble_config = simple_ble_init();
     if (ble_config == NULL) {
         ESP_LOGE(TAG, "Ran out of memory for BLE config");
@@ -460,9 +587,13 @@ esp_err_t protocomm_ble_start(protocomm_t *pc, const protocomm_ble_config_t *con
     ble_config->set_mtu_fn      = transport_simple_ble_set_mtu;
 
     /* Set parameters required for advertising */
-    ble_config->adv_data        = adv_data;
     ble_config->adv_params      = adv_params;
 
+    ble_config->raw_adv_data_p        = protoble_internal->raw_adv_data_p;
+    ble_config->raw_adv_data_len      = protoble_internal->raw_adv_data_len;
+    ble_config->raw_scan_rsp_data_p   = protoble_internal->raw_scan_rsp_data_p;
+    ble_config->raw_scan_rsp_data_len = protoble_internal->raw_scan_rsp_data_len;
+
     ble_config->device_name     = protocomm_ble_device_name;
     ble_config->gatt_db_count   = populate_gatt_db(&ble_config->gatt_db);
 

+ 7 - 0
tools/esp_prov/esp_prov.py

@@ -60,6 +60,13 @@ def get_transport(sel_transport, softap_endpoint=None, ble_devname=None):
         if (sel_transport == 'softap'):
             tp = transport.Transport_Softap(softap_endpoint)
         elif (sel_transport == 'ble'):
+            # BLE client is now capable of automatically figuring out
+            # the primary service from the advertisement data and the
+            # characteristics corresponding to each endpoint.
+            # Below, the service_uuid field and 16bit UUIDs in the nu_lookup
+            # table are provided only to support devices running older firmware,
+            # in which case, the automated discovery will fail and the client
+            # will fallback to using the provided UUIDs instead
             tp = transport.Transport_BLE(devname=ble_devname,
                                          service_uuid='0000ffff-0000-1000-8000-00805f9b34fb',
                                          nu_lookup={'prov-session': 'ff51',

+ 98 - 26
tools/esp_prov/transport/ble_cli.py

@@ -15,6 +15,7 @@
 
 from __future__ import print_function
 from builtins import input
+from future.utils import iteritems
 
 import platform
 
@@ -40,20 +41,24 @@ if platform.system() == 'Linux':
 
 # BLE client (Linux Only) using Bluez and DBus
 class BLE_Bluez_Client:
-    def connect(self, devname, iface, srv_uuid):
+    def connect(self, devname, iface, chrc_names, fallback_srv_uuid):
         self.devname = devname
-        self.srv_uuid = srv_uuid
+        self.srv_uuid_fallback = fallback_srv_uuid
+        self.chrc_names = [name.lower() for name in chrc_names]
         self.device = None
         self.adapter = None
         self.adapter_props = None
         self.services = None
+        self.nu_lookup = None
+        self.characteristics = dict()
+        self.srv_uuid_adv = None
 
         dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
         bus = dbus.SystemBus()
         manager = dbus.Interface(bus.get_object("org.bluez", "/"), "org.freedesktop.DBus.ObjectManager")
         objects = manager.GetManagedObjects()
 
-        for path, interfaces in objects.items():
+        for path, interfaces in iteritems(objects):
             adapter = interfaces.get("org.bluez.Adapter1")
             if adapter is not None:
                 if path.endswith(iface):
@@ -94,8 +99,8 @@ class BLE_Bluez_Client:
         manager = dbus.Interface(bus.get_object("org.bluez", "/"), "org.freedesktop.DBus.ObjectManager")
         objects = manager.GetManagedObjects()
         dev_path = None
-        for path, interfaces in objects.items():
-            if "org.bluez.Device1" not in interfaces.keys():
+        for path, interfaces in iteritems(objects):
+            if "org.bluez.Device1" not in interfaces:
                 continue
             if interfaces["org.bluez.Device1"].get("Name") == self.devname:
                 dev_path = path
@@ -106,6 +111,19 @@ class BLE_Bluez_Client:
 
         try:
             self.device = bus.get_object("org.bluez", dev_path)
+            try:
+                uuids = self.device.Get('org.bluez.Device1', 'UUIDs',
+                                        dbus_interface='org.freedesktop.DBus.Properties')
+                # There should be 1 service UUID in advertising data
+                # If bluez had cached an old version of the advertisement data
+                # the list of uuids may be incorrect, in which case connection
+                # or service discovery may fail the first time. If that happens
+                # the cache will be refreshed before next retry
+                if len(uuids) == 1:
+                    self.srv_uuid_adv = uuids[0]
+            except dbus.exceptions.DBusException as e:
+                print(e)
+
             self.device.Connect(dbus_interface='org.bluez.Device1')
         except Exception as e:
             print(e)
@@ -116,35 +134,84 @@ class BLE_Bluez_Client:
         bus = dbus.SystemBus()
         manager = dbus.Interface(bus.get_object("org.bluez", "/"), "org.freedesktop.DBus.ObjectManager")
         objects = manager.GetManagedObjects()
-        srv_path = None
-        for path, interfaces in objects.items():
-            if "org.bluez.GattService1" not in interfaces.keys():
+        service_found = False
+        for srv_path, srv_interfaces in iteritems(objects):
+            if "org.bluez.GattService1" not in srv_interfaces:
+                continue
+            if not srv_path.startswith(self.device.object_path):
                 continue
-            if path.startswith(self.device.object_path):
-                service = bus.get_object("org.bluez", path)
-                uuid = service.Get('org.bluez.GattService1', 'UUID',
+            service = bus.get_object("org.bluez", srv_path)
+            srv_uuid = service.Get('org.bluez.GattService1', 'UUID',
                                    dbus_interface='org.freedesktop.DBus.Properties')
-                if uuid == self.srv_uuid:
-                    srv_path = path
+
+            # If service UUID doesn't match the one found in advertisement data
+            # then also check if it matches the fallback UUID
+            if srv_uuid not in [self.srv_uuid_adv, self.srv_uuid_fallback]:
+                continue
+
+            nu_lookup = dict()
+            characteristics = dict()
+            for chrc_path, chrc_interfaces in iteritems(objects):
+                if "org.bluez.GattCharacteristic1" not in chrc_interfaces:
+                    continue
+                if not chrc_path.startswith(service.object_path):
+                    continue
+                chrc = bus.get_object("org.bluez", chrc_path)
+                uuid = chrc.Get('org.bluez.GattCharacteristic1', 'UUID',
+                                dbus_interface='org.freedesktop.DBus.Properties')
+                characteristics[uuid] = chrc
+                for desc_path, desc_interfaces in iteritems(objects):
+                    if "org.bluez.GattDescriptor1" not in desc_interfaces:
+                        continue
+                    if not desc_path.startswith(chrc.object_path):
+                        continue
+                    desc = bus.get_object("org.bluez", desc_path)
+                    desc_uuid = desc.Get('org.bluez.GattDescriptor1', 'UUID',
+                                         dbus_interface='org.freedesktop.DBus.Properties')
+                    if desc_uuid[4:8] != '2901':
+                        continue
+                    try:
+                        readval = desc.ReadValue({}, dbus_interface='org.bluez.GattDescriptor1')
+                    except dbus.exceptions.DBusException:
+                        break
+                    found_name = ''.join(chr(b) for b in readval).lower()
+                    nu_lookup[found_name] = uuid
                     break
 
-        if srv_path is None:
+            match_found = True
+            for name in self.chrc_names:
+                if name not in nu_lookup:
+                    # Endpoint name not present
+                    match_found = False
+                    break
+
+            # Create lookup table only if all endpoint names found
+            self.nu_lookup = [None, nu_lookup][match_found]
+            self.characteristics = characteristics
+            service_found = True
+
+            # If the service UUID matches that in the advertisement
+            # we can stop the search now. If it doesn't match, we
+            # have found the service corresponding to the fallback
+            # UUID, in which case don't break and keep searching
+            # for the advertised service
+            if srv_uuid == self.srv_uuid_adv:
+                break
+
+        if not service_found:
             self.device.Disconnect(dbus_interface='org.bluez.Device1')
+            if self.adapter:
+                self.adapter.RemoveDevice(self.device)
             self.device = None
+            self.nu_lookup = None
+            self.characteristics = dict()
             raise RuntimeError("Provisioning service not found")
 
-        self.characteristics = dict()
-        for path, interfaces in objects.items():
-            if "org.bluez.GattCharacteristic1" not in interfaces.keys():
-                continue
-            if path.startswith(srv_path):
-                chrc = bus.get_object("org.bluez", path)
-                uuid = chrc.Get('org.bluez.GattCharacteristic1', 'UUID',
-                                dbus_interface='org.freedesktop.DBus.Properties')
-                self.characteristics[uuid] = chrc
+    def get_nu_lookup(self):
+        return self.nu_lookup
 
     def has_characteristic(self, uuid):
-        if uuid in self.characteristics.keys():
+        if uuid in self.characteristics:
             return True
         return False
 
@@ -154,6 +221,8 @@ class BLE_Bluez_Client:
             if self.adapter:
                 self.adapter.RemoveDevice(self.device)
             self.device = None
+            self.nu_lookup = None
+            self.characteristics = dict()
         if self.adapter_props:
             self.adapter_props.Set("org.bluez.Adapter1", "Powered", dbus.Boolean(0))
 
@@ -180,7 +249,7 @@ class BLE_Bluez_Client:
 
 # Console based BLE client for Cross Platform support
 class BLE_Console_Client:
-    def connect(self, devname, iface, srv_uuid):
+    def connect(self, devname, iface, chrc_names, fallback_srv_uuid):
         print("BLE client is running in console mode")
         print("\tThis could be due to your platform not being supported or dependencies not being met")
         print("\tPlease ensure all pre-requisites are met to run the full fledged client")
@@ -189,11 +258,14 @@ class BLE_Console_Client:
         if resp != 'Y' and resp != 'y':
             return False
         print("BLECLI >> List available attributes of the connected device")
-        resp = input("BLECLI >> Is the service UUID '" + srv_uuid + "' listed among available attributes? [y/n] ")
+        resp = input("BLECLI >> Is the service UUID '" + fallback_srv_uuid + "' listed among available attributes? [y/n] ")
         if resp != 'Y' and resp != 'y':
             return False
         return True
 
+    def get_nu_lookup(self):
+        return None
+
     def has_characteristic(self, uuid):
         resp = input("BLECLI >> Is the characteristic UUID '" + uuid + "' listed among available attributes? [y/n] ")
         if resp != 'Y' and resp != 'y':

+ 15 - 6
tools/esp_prov/transport/transport_ble.py

@@ -27,19 +27,28 @@ class Transport_BLE(Transport):
             # Calculate characteristic UUID for each endpoint
             nu_lookup[name] = service_uuid[:4] + '{:02x}'.format(
                 int(nu_lookup[name], 16) & int(service_uuid[4:8], 16)) + service_uuid[8:]
-        self.name_uuid_lookup = nu_lookup
 
         # Get BLE client module
         self.cli = ble_cli.get_client()
 
         # Use client to connect to BLE device and bind to service
-        if not self.cli.connect(devname=devname, iface='hci0', srv_uuid=service_uuid):
+        if not self.cli.connect(devname=devname, iface='hci0',
+                                chrc_names=nu_lookup.keys(),
+                                fallback_srv_uuid=service_uuid):
             raise RuntimeError("Failed to initialize transport")
 
-        # Check if expected characteristics are provided by the service
-        for name in self.name_uuid_lookup.keys():
-            if not self.cli.has_characteristic(self.name_uuid_lookup[name]):
-                raise RuntimeError("'" + name + "' endpoint not found")
+        # Irrespective of provided parameters, let the client
+        # generate a lookup table by reading advertisement data
+        # and characteristic user descriptors
+        self.name_uuid_lookup = self.cli.get_nu_lookup()
+
+        # If that doesn't work, use the lookup table provided as parameter
+        if self.name_uuid_lookup is None:
+            self.name_uuid_lookup = nu_lookup
+            # Check if expected characteristics are provided by the service
+            for name in self.name_uuid_lookup.keys():
+                if not self.cli.has_characteristic(self.name_uuid_lookup[name]):
+                    raise RuntimeError("'" + name + "' endpoint not found")
 
     def __del__(self):
         # Make sure device is disconnected before application gets closed