Merge branch 'protocomm_ble_128bit_uuid_v3.3' into 'release/v3.3'

protocomm_ble : Fix support for custom service UUIDs (backport v3.3)

See merge request idf/esp-idf!5017
This commit is contained in:
Angus Gratton 2019-06-07 07:47:19 +08:00
commit fb6f343ce5
9 changed files with 407 additions and 129 deletions

View file

@ -14,6 +14,8 @@
#pragma once #pragma once
#include <esp_gap_ble_api.h>
#include <protocomm.h> #include <protocomm.h>
#ifdef __cplusplus #ifdef __cplusplus
@ -22,8 +24,9 @@ extern "C" {
/** /**
* BLE device name cannot be larger than this value * 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 * @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 * BLE device name being broadcast at the time of provisioning
*/ */
char device_name[MAX_BLE_DEVNAME_LEN]; 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 * Pointer to the Name-UUID lookup table

View file

@ -30,25 +30,37 @@ static const char *TAG = "simple_ble";
static simple_ble_cfg_t *g_ble_cfg_p; static simple_ble_cfg_t *g_ble_cfg_p;
static uint16_t g_gatt_table_map[SIMPLE_BLE_MAX_GATT_TABLE_SIZE]; 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++) { for (int i = 0; i < SIMPLE_BLE_MAX_GATT_TABLE_SIZE; i++) {
if (g_gatt_table_map[i] == handle) { if (g_gatt_table_map[i] == handle) {
uuid_ptr = (uint16_t *) g_ble_cfg_p->gatt_db[i].att_desc.uuid_p; uuid128_ptr = (const uint8_t *) g_ble_cfg_p->gatt_db[i].att_desc.uuid_p;
return *uuid_ptr; 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) static void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param)
{ {
switch (event) { switch (event) {
case ESP_GAP_BLE_ADV_DATA_SET_COMPLETE_EVT: case ESP_GAP_BLE_ADV_DATA_RAW_SET_COMPLETE_EVT:
esp_ble_gap_start_advertising(&g_ble_cfg_p->adv_params); 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; break;
default: default:
break; 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); ESP_LOGE(TAG, "set device name failed, error code = 0x%x", ret);
return; 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) { if (ret) {
ESP_LOGE(TAG, "config adv data failed, error code = 0x%x", ret); ESP_LOGE(TAG, "config raw adv data failed, error code = 0x%x ", ret);
return; 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 raw scan rsp data failed, error code = 0x%x", ret);
return;
}
adv_config_done |= scan_rsp_config_flag;
break; break;
case ESP_GATTS_READ_EVT: case ESP_GATTS_READ_EVT:
g_ble_cfg_p->read_fn(event, gatts_if, param); g_ble_cfg_p->read_fn(event, gatts_if, param);

View file

@ -32,11 +32,16 @@ typedef void (simple_ble_cb_t)(esp_gatts_cb_event_t event, esp_gatt_if_t p_gatts
typedef struct { typedef struct {
/** Name to be displayed to devices scanning for ESP32 */ /** Name to be displayed to devices scanning for ESP32 */
const char *device_name; const char *device_name;
/** Advertising data content, according to "Supplement to the Bluetooth Core Specification" */ /** Raw advertisement data */
esp_ble_adv_data_t adv_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 */ /** Parameters to configure the nature of advertising */
esp_ble_adv_params_t adv_params; 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; esp_gatts_attr_db_t *gatt_db;
/** Number of entries in the gatt_db descriptor table */ /** Number of entries in the gatt_db descriptor table */
ssize_t gatt_db_count; 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(); 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 * This function can be easily used to get the corresponding
* UUID for a characteristic that has been created, and the one for * UUID for a characteristic that has been created, and the one for
* which we only have the handle 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_ */ #endif /* _SIMPLE_BLE_ */

View file

@ -15,6 +15,7 @@
#include <sys/param.h> #include <sys/param.h>
#include <esp_log.h> #include <esp_log.h>
#include <esp_gatt_common_api.h> #include <esp_gatt_common_api.h>
#include <esp_gap_bt_api.h>
#include <protocomm.h> #include <protocomm.h>
#include <protocomm_ble.h> #include <protocomm_ble.h>
@ -28,10 +29,17 @@
static const char *TAG = "protocomm_ble"; static const char *TAG = "protocomm_ble";
/* BLE specific configuration parameters */ /* BLE specific configuration parameters */
const uint16_t GATTS_SERVICE_UUID_PROV = 0xFFFF; static const uint16_t primary_service_uuid = ESP_GATT_UUID_PRI_SERVICE;
const uint16_t primary_service_uuid = ESP_GATT_UUID_PRI_SERVICE; static const uint16_t character_declaration_uuid = ESP_GATT_UUID_CHAR_DECLARE;
const uint16_t character_declaration_uuid = ESP_GATT_UUID_CHAR_DECLARE; static const uint16_t character_user_description = ESP_GATT_UUID_CHAR_DESCRIPTION;
const uint8_t char_prop_read_write = ESP_GATT_CHAR_PROP_BIT_READ | ESP_GATT_CHAR_PROP_BIT_WRITE; 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 { typedef struct {
uint8_t *prepare_buf; uint8_t *prepare_buf;
@ -41,11 +49,21 @@ typedef struct {
static prepare_type_env_t prepare_write_env; 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 { typedef struct _protocomm_ble {
protocomm_t *pc_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; ssize_t g_nu_lookup_count;
uint16_t gatt_mtu; 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; } _protocomm_ble_internal_t;
static _protocomm_ble_internal_t *protoble_internal; 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; 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) static void hexdump(const char *msg, uint8_t *buf, int len)
{ {
ESP_LOGD(TAG, "%s:", msg); ESP_LOGD(TAG, "%s:", msg);
ESP_LOG_BUFFER_HEX_LEVEL(TAG, buf, len, ESP_LOG_DEBUG); 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) 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++) { 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; 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) 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; esp_err_t err;
@ -314,12 +322,17 @@ static esp_err_t protocomm_ble_remove_endpoint(const char *ep_name)
return ESP_OK; return ESP_OK;
} }
static ssize_t populate_gatt_db(esp_gatts_attr_db_t **gatt_db_generated) static ssize_t populate_gatt_db(esp_gatts_attr_db_t **gatt_db_generated)
{ {
int i; int i;
/* We need esp_gatts_attr_db_t of size 2 * number of handlers + 1 for service */ /* Each endpoint requires 3 attributes:
ssize_t gatt_db_generated_entries = 2 * protoble_internal->g_nu_lookup_count + 1; * 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 = (esp_gatts_attr_db_t *) malloc(sizeof(esp_gatts_attr_db_t) *
(gatt_db_generated_entries)); (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_length = ESP_UUID_LEN_16;
(*gatt_db_generated)[0].att_desc.uuid_p = (uint8_t *) &primary_service_uuid; (*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.perm = ESP_GATT_PERM_READ;
(*gatt_db_generated)[0].att_desc.max_length = sizeof(uint16_t); (*gatt_db_generated)[0].att_desc.max_length = ESP_UUID_LEN_128;
(*gatt_db_generated)[0].att_desc.length = sizeof(GATTS_SERVICE_UUID_PROV); (*gatt_db_generated)[0].att_desc.length = ESP_UUID_LEN_128;
(*gatt_db_generated)[0].att_desc.value = (uint8_t *) &GATTS_SERVICE_UUID_PROV; (*gatt_db_generated)[0].att_desc.value = protoble_internal->service_uuid;
/* Declare characteristics */ /* Declare characteristics */
for (i = 1 ; i < gatt_db_generated_entries ; i++) { 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].attr_control.auto_rsp = ESP_GATT_RSP_BY_APP;
(*gatt_db_generated)[i].att_desc.uuid_length = ESP_UUID_LEN_16; if (i % 3 == 1) {
(*gatt_db_generated)[i].att_desc.perm = ESP_GATT_PERM_READ | /* Characteristic Declaration */
ESP_GATT_PERM_WRITE; (*gatt_db_generated)[i].att_desc.perm = ESP_GATT_PERM_READ;
(*gatt_db_generated)[i].att_desc.uuid_length = ESP_UUID_LEN_16;
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.uuid_p = (uint8_t *) &character_declaration_uuid; (*gatt_db_generated)[i].att_desc.max_length = sizeof(uint8_t);
(*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.length = sizeof(uint8_t); (*gatt_db_generated)[i].att_desc.value = (uint8_t *) &character_prop_read_write;
(*gatt_db_generated)[i].att_desc.value = (uint8_t *) &char_prop_read_write; } else if (i % 3 == 2) {
} else { /* Char Value */ /* Characteristic 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.perm = ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE;
(*gatt_db_generated)[i].att_desc.max_length = CHAR_VAL_LEN_MAX; (*gatt_db_generated)[i].att_desc.uuid_length = ESP_UUID_LEN_128;
(*gatt_db_generated)[i].att_desc.length = 0; (*gatt_db_generated)[i].att_desc.uuid_p = protoble_internal->g_nu_lookup[i / 3].uuid128;
(*gatt_db_generated)[i].att_desc.value = NULL; (*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; return gatt_db_generated_entries;
@ -371,6 +394,8 @@ static void protocomm_ble_cleanup(void)
} }
free(protoble_internal->g_nu_lookup); free(protoble_internal->g_nu_lookup);
} }
free(protoble_internal->raw_adv_data_p);
free(protoble_internal->raw_scan_rsp_data_p);
free(protoble_internal); free(protoble_internal);
protoble_internal = NULL; protoble_internal = NULL;
} }
@ -378,11 +403,6 @@ static void protocomm_ble_cleanup(void)
free(protocomm_ble_device_name); free(protocomm_ble_device_name);
protocomm_ble_device_name = NULL; 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) 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; 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 */ /* Store BLE device name internally */
protocomm_ble_device_name = strdup(config->device_name); protocomm_ble_device_name = strdup(config->device_name);
if (protocomm_ble_device_name == NULL) { 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; return ESP_ERR_NO_MEM;
} }
protoble_internal->g_nu_lookup_count = config->nu_lookup_count; 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 = malloc(config->nu_lookup_count * sizeof(name_uuid128_t));
if (protoble_internal->g_nu_lookup == NULL) { if (protoble_internal->g_nu_lookup == NULL) {
ESP_LOGE(TAG, "Error allocating internal name UUID table"); ESP_LOGE(TAG, "Error allocating internal name UUID table");
protocomm_ble_cleanup(); 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++) { 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); protoble_internal->g_nu_lookup[i].name = strdup(config->nu_lookup[i].name);
if (protoble_internal->g_nu_lookup[i].name == NULL) { if (protoble_internal->g_nu_lookup[i].name == NULL) {
ESP_LOGE(TAG, "Error allocating internal name UUID entry"); 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->pc_ble = pc;
protoble_internal->gatt_mtu = ESP_GATT_DEF_BLE_MTU_SIZE; 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(); simple_ble_cfg_t *ble_config = simple_ble_init();
if (ble_config == NULL) { if (ble_config == NULL) {
ESP_LOGE(TAG, "Ran out of memory for BLE config"); 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; ble_config->set_mtu_fn = transport_simple_ble_set_mtu;
/* Set parameters required for advertising */ /* Set parameters required for advertising */
ble_config->adv_data = adv_data;
ble_config->adv_params = adv_params; 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->device_name = protocomm_ble_device_name;
ble_config->gatt_db_count = populate_gatt_db(&ble_config->gatt_db); ble_config->gatt_db_count = populate_gatt_db(&ble_config->gatt_db);

View file

@ -59,9 +59,9 @@ static esp_err_t app_prov_start_service(void)
/* Endpoint UUIDs */ /* Endpoint UUIDs */
protocomm_ble_name_uuid_t nu_lookup_table[] = { protocomm_ble_name_uuid_t nu_lookup_table[] = {
{"prov-session", 0xFF51}, {"prov-session", 0x0001},
{"prov-config", 0xFF52}, {"prov-config", 0x0002},
{"proto-ver", 0xFF53}, {"proto-ver", 0x0003},
}; };
/* Config for protocomm_ble_start() */ /* Config for protocomm_ble_start() */
@ -69,12 +69,33 @@ static esp_err_t app_prov_start_service(void)
.service_uuid = { .service_uuid = {
/* LSB <--------------------------------------- /* LSB <---------------------------------------
* ---------------------------------------> MSB */ * ---------------------------------------> MSB */
0xfb, 0x34, 0x9b, 0x5f, 0x80, 0x00, 0x00, 0x80, 0xb4, 0xdf, 0x5a, 0x1c, 0x3f, 0x6b, 0xf4, 0xbf,
0x00, 0x10, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xea, 0x4a, 0x82, 0x03, 0x04, 0x90, 0x1a, 0x02,
}, },
.nu_lookup_count = sizeof(nu_lookup_table)/sizeof(nu_lookup_table[0]), .nu_lookup_count = sizeof(nu_lookup_table)/sizeof(nu_lookup_table[0]),
.nu_lookup = nu_lookup_table .nu_lookup = nu_lookup_table
}; };
/* With the above selection of 128bit primary service UUID and
* 16bit endpoint UUIDs, the 128bit characteristic UUIDs will be
* formed by replacing the 12th and 13th bytes of the service UUID
* with the 16bit endpoint UUID, i.e. :
* service UUID : 021a9004-0382-4aea-bff4-6b3f1c5adfb4
* masked base : 021a____-0382-4aea-bff4-6b3f1c5adfb4
* ------------------------------------------------------
* resulting characteristic UUIDs :
* 1) prov-session : 021a0001-0382-4aea-bff4-6b3f1c5adfb4
* 2) prov-config : 021a0002-0382-4aea-bff4-6b3f1c5adfb4
* 3) proto-ver : 021a0003-0382-4aea-bff4-6b3f1c5adfb4
*
* Also, note that each endpoint (characteristic) will have
* an associated "Characteristic User Description" descriptor
* with 16bit UUID 0x2901, each containing the corresponding
* endpoint name. These descriptors can be used by a client
* side application to figure out which characteristic is
* mapped to which endpoint, without having to hardcode the
* UUIDs */
uint8_t eth_mac[6]; uint8_t eth_mac[6];
esp_wifi_get_mac(WIFI_IF_STA, eth_mac); esp_wifi_get_mac(WIFI_IF_STA, eth_mac);
snprintf(config.device_name, sizeof(config.device_name), "%s%02X%02X%02X", snprintf(config.device_name, sizeof(config.device_name), "%s%02X%02X%02X",

View file

@ -13,12 +13,12 @@ python esp_prov.py --transport < mode of provisioning : softap \ ble \ console >
Usage of `esp-prov` assumes that the provisioning app has specific protocomm endpoints active. These endpoints are active in the provisioning examples and accept specific protobuf data structures: Usage of `esp-prov` assumes that the provisioning app has specific protocomm endpoints active. These endpoints are active in the provisioning examples and accept specific protobuf data structures:
| Endpoint Name | URI (HTTP server on ip:port) | UUID (BLE) | Description | | Endpoint Name | URI (HTTP server on ip:port) | Description |
|---------------|------------------------------|--------------------------------------|-----------------------------------------------------------| |---------------|------------------------------|-----------------------------------------------------------|
| prov-session | http://ip:port/prov-session | 0000ff51-0000-1000-8000-00805f9b34fb | Security endpoint used for session establishment | | prov-session | http://ip:port/prov-session | Security endpoint used for session establishment |
| prov-config | http://ip:port/prov-config | 0000ff52-0000-1000-8000-00805f9b34fb | Endpoint used for configuring Wi-Fi credentials on device | | prov-config | http://ip:port/prov-config | Endpoint used for configuring Wi-Fi credentials on device |
| proto-ver | http://ip:port/proto-ver | 0000ff53-0000-1000-8000-00805f9b34fb | Version endpoint for checking protocol compatibility | | proto-ver | http://ip:port/proto-ver | Version endpoint for checking protocol compatibility |
| custom-config | http://ip:port/custom-config | NA | Optional endpoint for configuring custom credentials | | custom-config | http://ip:port/custom-config | Optional endpoint for configuring custom credentials |
# PARAMETERS # PARAMETERS

View file

@ -60,6 +60,13 @@ def get_transport(sel_transport, softap_endpoint=None, ble_devname=None):
if (sel_transport == 'softap'): if (sel_transport == 'softap'):
tp = transport.Transport_Softap(softap_endpoint) tp = transport.Transport_Softap(softap_endpoint)
elif (sel_transport == 'ble'): 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, tp = transport.Transport_BLE(devname=ble_devname,
service_uuid='0000ffff-0000-1000-8000-00805f9b34fb', service_uuid='0000ffff-0000-1000-8000-00805f9b34fb',
nu_lookup={'prov-session': 'ff51', nu_lookup={'prov-session': 'ff51',

View file

@ -15,6 +15,7 @@
from __future__ import print_function from __future__ import print_function
from builtins import input from builtins import input
from future.utils import iteritems
import platform import platform
@ -40,20 +41,24 @@ if platform.system() == 'Linux':
# BLE client (Linux Only) using Bluez and DBus # BLE client (Linux Only) using Bluez and DBus
class BLE_Bluez_Client: 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.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.device = None
self.adapter = None self.adapter = None
self.adapter_props = None self.adapter_props = None
self.services = None self.services = None
self.nu_lookup = None
self.characteristics = dict()
self.srv_uuid_adv = None
dbus.mainloop.glib.DBusGMainLoop(set_as_default=True) dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
bus = dbus.SystemBus() bus = dbus.SystemBus()
manager = dbus.Interface(bus.get_object("org.bluez", "/"), "org.freedesktop.DBus.ObjectManager") manager = dbus.Interface(bus.get_object("org.bluez", "/"), "org.freedesktop.DBus.ObjectManager")
objects = manager.GetManagedObjects() objects = manager.GetManagedObjects()
for path, interfaces in objects.items(): for path, interfaces in iteritems(objects):
adapter = interfaces.get("org.bluez.Adapter1") adapter = interfaces.get("org.bluez.Adapter1")
if adapter is not None: if adapter is not None:
if path.endswith(iface): if path.endswith(iface):
@ -94,8 +99,8 @@ class BLE_Bluez_Client:
manager = dbus.Interface(bus.get_object("org.bluez", "/"), "org.freedesktop.DBus.ObjectManager") manager = dbus.Interface(bus.get_object("org.bluez", "/"), "org.freedesktop.DBus.ObjectManager")
objects = manager.GetManagedObjects() objects = manager.GetManagedObjects()
dev_path = None dev_path = None
for path, interfaces in objects.items(): for path, interfaces in iteritems(objects):
if "org.bluez.Device1" not in interfaces.keys(): if "org.bluez.Device1" not in interfaces:
continue continue
if interfaces["org.bluez.Device1"].get("Name") == self.devname: if interfaces["org.bluez.Device1"].get("Name") == self.devname:
dev_path = path dev_path = path
@ -106,6 +111,19 @@ class BLE_Bluez_Client:
try: try:
self.device = bus.get_object("org.bluez", dev_path) 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') self.device.Connect(dbus_interface='org.bluez.Device1')
except Exception as e: except Exception as e:
print(e) print(e)
@ -116,35 +134,84 @@ class BLE_Bluez_Client:
bus = dbus.SystemBus() bus = dbus.SystemBus()
manager = dbus.Interface(bus.get_object("org.bluez", "/"), "org.freedesktop.DBus.ObjectManager") manager = dbus.Interface(bus.get_object("org.bluez", "/"), "org.freedesktop.DBus.ObjectManager")
objects = manager.GetManagedObjects() objects = manager.GetManagedObjects()
srv_path = None service_found = False
for path, interfaces in objects.items(): for srv_path, srv_interfaces in iteritems(objects):
if "org.bluez.GattService1" not in interfaces.keys(): if "org.bluez.GattService1" not in srv_interfaces:
continue continue
if path.startswith(self.device.object_path): if not srv_path.startswith(self.device.object_path):
service = bus.get_object("org.bluez", path) continue
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') dbus_interface='org.freedesktop.DBus.Properties')
if uuid == self.srv_uuid:
srv_path = path
break
if srv_path is None: # If service UUID doesn't match the one found in advertisement data
self.device.Disconnect(dbus_interface='org.bluez.Device1') # then also check if it matches the fallback UUID
self.device = None if srv_uuid not in [self.srv_uuid_adv, self.srv_uuid_fallback]:
raise RuntimeError("Provisioning service not found")
self.characteristics = dict()
for path, interfaces in objects.items():
if "org.bluez.GattCharacteristic1" not in interfaces.keys():
continue continue
if path.startswith(srv_path):
chrc = bus.get_object("org.bluez", path) 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', uuid = chrc.Get('org.bluez.GattCharacteristic1', 'UUID',
dbus_interface='org.freedesktop.DBus.Properties') dbus_interface='org.freedesktop.DBus.Properties')
self.characteristics[uuid] = chrc 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
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")
def get_nu_lookup(self):
return self.nu_lookup
def has_characteristic(self, uuid): def has_characteristic(self, uuid):
if uuid in self.characteristics.keys(): if uuid in self.characteristics:
return True return True
return False return False
@ -154,6 +221,8 @@ class BLE_Bluez_Client:
if self.adapter: if self.adapter:
self.adapter.RemoveDevice(self.device) self.adapter.RemoveDevice(self.device)
self.device = None self.device = None
self.nu_lookup = None
self.characteristics = dict()
if self.adapter_props: if self.adapter_props:
self.adapter_props.Set("org.bluez.Adapter1", "Powered", dbus.Boolean(0)) 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 # Console based BLE client for Cross Platform support
class BLE_Console_Client: 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("BLE client is running in console mode")
print("\tThis could be due to your platform not being supported or dependencies not being met") 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") 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': if resp != 'Y' and resp != 'y':
return False return False
print("BLECLI >> List available attributes of the connected device") 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': if resp != 'Y' and resp != 'y':
return False return False
return True return True
def get_nu_lookup(self):
return None
def has_characteristic(self, uuid): def has_characteristic(self, uuid):
resp = input("BLECLI >> Is the characteristic UUID '" + uuid + "' listed among available attributes? [y/n] ") resp = input("BLECLI >> Is the characteristic UUID '" + uuid + "' listed among available attributes? [y/n] ")
if resp != 'Y' and resp != 'y': if resp != 'Y' and resp != 'y':

View file

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