Merge branch 'protocomm_ble_128bit_uuid_v3.2' into 'release/v3.2'
protocomm_ble : Fix support for custom service UUIDs (backport v3.2) See merge request idf/esp-idf!5018
This commit is contained in:
commit
725ecf108f
9 changed files with 415 additions and 135 deletions
|
@ -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
|
||||||
|
|
|
@ -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:
|
||||||
|
adv_config_done &= (~adv_config_flag);
|
||||||
|
if (adv_config_done == 0) {
|
||||||
esp_ble_gap_start_advertising(&g_ble_cfg_p->adv_params);
|
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);
|
||||||
|
|
|
@ -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_ */
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
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_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.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 *) &char_prop_read_write;
|
(*gatt_db_generated)[i].att_desc.value = (uint8_t *) &character_prop_read_write;
|
||||||
} else { /* Char Value */
|
} else if (i % 3 == 2) {
|
||||||
(*gatt_db_generated)[i].att_desc.uuid_p = (uint8_t *)&protoble_internal->g_nu_lookup[i / 2 - 1].uuid;
|
/* 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.max_length = CHAR_VAL_LEN_MAX;
|
||||||
(*gatt_db_generated)[i].att_desc.length = 0;
|
(*gatt_db_generated)[i].att_desc.length = 0;
|
||||||
(*gatt_db_generated)[i].att_desc.value = NULL;
|
(*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) {
|
||||||
|
@ -422,7 +432,7 @@ esp_err_t protocomm_ble_start(protocomm_t *pc, const protocomm_ble_config_t *con
|
||||||
}
|
}
|
||||||
|
|
||||||
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);
|
||||||
|
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -42,10 +42,16 @@ 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 = {
|
nu_lookup={'prov-session': 'ff51',
|
||||||
'prov-session': 'ff51',
|
|
||||||
'prov-config': 'ff52',
|
'prov-config': 'ff52',
|
||||||
'proto-ver': 'ff53'
|
'proto-ver': 'ff53'
|
||||||
})
|
})
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
@ -37,20 +38,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 != None:
|
if adapter != None:
|
||||||
if path.endswith(iface):
|
if path.endswith(iface):
|
||||||
|
@ -91,8 +96,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
|
||||||
|
@ -103,6 +108,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)
|
||||||
|
@ -113,33 +131,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 == None:
|
# If service UUID doesn't match the one found in advertisement data
|
||||||
raise RuntimeError("Provisioning service not found")
|
# then also check if it matches the fallback UUID
|
||||||
|
if srv_uuid not in [self.srv_uuid_adv, self.srv_uuid_fallback]:
|
||||||
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
|
||||||
|
|
||||||
|
@ -148,6 +217,9 @@ class BLE_Bluez_Client:
|
||||||
self.device.Disconnect(dbus_interface='org.bluez.Device1')
|
self.device.Disconnect(dbus_interface='org.bluez.Device1')
|
||||||
if self.adapter:
|
if self.adapter:
|
||||||
self.adapter.RemoveDevice(self.device)
|
self.adapter.RemoveDevice(self.device)
|
||||||
|
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))
|
||||||
|
|
||||||
|
@ -163,7 +235,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")
|
||||||
|
@ -172,11 +244,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':
|
||||||
|
|
|
@ -26,15 +26,24 @@ 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")
|
||||||
|
|
||||||
|
# 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
|
# Check if expected characteristics are provided by the service
|
||||||
for name in self.name_uuid_lookup.keys():
|
for name in self.name_uuid_lookup.keys():
|
||||||
if not self.cli.has_characteristic(self.name_uuid_lookup[name]):
|
if not self.cli.has_characteristic(self.name_uuid_lookup[name]):
|
||||||
|
|
Loading…
Reference in a new issue