Search code examples
bluetoothgattesp32

ESP-IDF v3.0 Bluetooth gatt server example or help using multiple services?


I'm currently working on a firmware project where I need the device to broadcast multiple Bluetooth services such as Device Info Service, Battery Services, etc... I've combed through the esp-idf GitHub and readthedocs site and all the examples for GATT Servers appear to only have a single service. As of right now, I've got advertising working and 1 service fully functional, but I'm absolutely stumped on how to get the second one working. If anyone has any examples or suggestions it would be much appreciated!

I've based most of this off of the ESP-IDF GATT Server Service Table example here: https://github.com/espressif/esp-idf/tree/master/examples/bluetooth/gatt_server_service_table

I'm initializing the Bluetooth libraries like this (throughout my code I've removed a lot of LOGs and error handling for readability... functionality is the same):

esp_err_t Bluetooth_Interface_Init(void)
{
    esp_err_t err;

    Bluetooth_Driver_Init(); //Takes care of bt hardware inits

    err = esp_ble_gatts_register_callback(gatts_event_handler);
    err = esp_ble_gap_register_callback(gap_event_handler);

    err = esp_ble_gatts_app_register(ESP_APP_ID);
    err = esp_ble_gatt_set_local_mtu(500);

    return err;
}

My Advertising Data here:

static uint8_t service_uuid[16] =
{
    /* LSB <--------------------------------------------------------------------------------> MSB */
    //first uuid, 16bit, [12],[13] is the value
    0xfb, 0x34, 0x9b, 0x5f, 0x80, 0x00, 0x00, 0x80, 0x00, 0x10, 0x00, 0x00, 0x18, 0x0D, 0x00, 0x00,
};

/* 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        = 0x20,
    .max_interval        = 0x40,
    .appearance          = 0x00,
    .manufacturer_len    = 0,    //TEST_MANUFACTURER_DATA_LEN,
    .p_manufacturer_data = NULL, //test_manufacturer,
    .service_data_len    = 0,
    .p_service_data      = NULL,
    .service_uuid_len    = sizeof(service_uuid),
    .p_service_uuid      = service_uuid,
    .flag = (ESP_BLE_ADV_FLAG_GEN_DISC | ESP_BLE_ADV_FLAG_BREDR_NOT_SPT),
};

// scan response data
static esp_ble_adv_data_t scan_rsp_data = {
    .set_scan_rsp        = true,
    .include_name        = true,
    .include_txpower     = true,
    .min_interval        = 0x20,
    .max_interval        = 0x40,
    .appearance          = 0x00,
    .manufacturer_len    = 0, //TEST_MANUFACTURER_DATA_LEN,
    .p_manufacturer_data = NULL, //&test_manufacturer[0],
    .service_data_len    = 0,
    .p_service_data      = NULL,
    .service_uuid_len    = sizeof(service_uuid),
    .p_service_uuid      = service_uuid,
    .flag = (ESP_BLE_ADV_FLAG_GEN_DISC | ESP_BLE_ADV_FLAG_BREDR_NOT_SPT),
};

static esp_ble_adv_params_t adv_params = {
    .adv_int_min         = 0x20,
    .adv_int_max         = 0x40,
    .adv_type            = ADV_TYPE_IND,
    .own_addr_type       = BLE_ADDR_TYPE_PUBLIC,
    .channel_map         = ADV_CHNL_ALL,
    .adv_filter_policy   = ADV_FILTER_ALLOW_SCAN_ANY_CON_ANY,
};

My GAP Event Handler:

   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:
                adv_config_done &= (~ADV_CONFIG_FLAG);
                if (adv_config_done == 0){
                    esp_ble_gap_start_advertising(&adv_params);
                }
                break;
            case ESP_GAP_BLE_SCAN_RSP_DATA_SET_COMPLETE_EVT:
                adv_config_done &= (~SCAN_RSP_CONFIG_FLAG);
                if (adv_config_done == 0){
                    esp_ble_gap_start_advertising(&adv_params);
                }
                break;

            case ESP_GAP_BLE_ADV_START_COMPLETE_EVT:
                /* advertising start complete event to indicate advertising start successfully or failed */
                if (param->adv_start_cmpl.status != ESP_BT_STATUS_SUCCESS) {
                    ESP_LOGE(TAG_IBLUETOOTH, "advertising start failed");
                }else{
                    ESP_LOGI(TAG_IBLUETOOTH, "advertising start successfully");
                }
                break;
            case ESP_GAP_BLE_ADV_STOP_COMPLETE_EVT:
                if (param->adv_stop_cmpl.status != ESP_BT_STATUS_SUCCESS) {
                    ESP_LOGE(TAG_IBLUETOOTH, "Advertising stop failed");
                }
                else {
                    ESP_LOGI(TAG_IBLUETOOTH, "Stop adv successfully\n");
                }
                break;
            case ESP_GAP_BLE_UPDATE_CONN_PARAMS_EVT:
                ESP_LOGI(TAG_IBLUETOOTH, "update connetion params status = %d, min_int = %d, max_int = %d,conn_int = %d,latency = %d, timeout = %d",
                      param->update_conn_params.status,
                      param->update_conn_params.min_int,
                      param->update_conn_params.max_int,
                      param->update_conn_params.conn_int,
                      param->update_conn_params.latency,
                      param->update_conn_params.timeout);
                break;
            default:
                break;
        }
    }

My GATTS Event Handler:

static void gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param)
{
#ifdef IBLUETOOTH_DEBUG
    ESP_LOGI(TAG_IBLUETOOTH, "gatts event handler: %d", event);
#endif
    /* If event is register event, store the gatts_if for each profile */
    if (event == ESP_GATTS_REG_EVT) {
        if (param->reg.status == ESP_GATT_OK) {


            bt_profile_tab[INFO_IDX].gatts_if = gatts_if; //This is all that works... It registers the info profile 
            //bt_profile_tab[param->reg.app_id].gatts_if = gatts_if; //Nothing gets registered when doing this
            //bt_profile_tab[WIFI_IDX].gatts_if = gatts_if; //Doing this always fails to register both.. Info Service is all that gets registered
        } else {
            ESP_LOGE(TAG_IBLUETOOTH, "reg app failed, app_id %04x, status %d", param->reg.app_id, param->reg.status);
            return;
        }
    }
    do {
        int idx;
        for (idx = 0; idx < PROFILE_NUM; idx++) {
            /* ESP_GATT_IF_NONE, not specify a certain gatt_if, need to call every profile cb function */
            if (gatts_if == ESP_GATT_IF_NONE || gatts_if == bt_profile_tab[idx].gatts_if) {
                if (bt_profile_tab[idx].gatts_cb) {
                    bt_profile_tab[idx].gatts_cb(event, gatts_if, param);
                }
            }
        }
    } while (0);
}

An example of one of the Service Event Handlers:

static const uint16_t INFO_SERV_uuid                = ESP_GATT_UUID_DEVICE_INFO_SVC;  //0x180A
static const uint16_t INFO_SERV_char_sysid_uuid         = ...;
static const uint16_t INFO_SERV_char_model_uuid         = ...;
static const uint16_t INFO_SERV_char_serial_uuid        = ...;
static const uint16_t INFO_SERV_char_fw_uuid        = ...;
static const uint16_t INFO_SERV_char_protocol_uuid  = ...;

static const uint8_t INFO_SERV_char_sysid_descr[]       = ...;
static const uint8_t INFO_SERV_char_model_descr[]       = ...;
static const uint8_t INFO_SERV_char_serial_descr[]      = ...;
static const uint8_t INFO_SERV_char_fw_descr[]      = ...;
static const uint8_t INFO_SERV_char_protocol_descr[]        = ...;

static const esp_gatts_attr_db_t info_serv_gatt_db[INFO_NB] =
{
    [INFO_SERV] = {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *) &primary_service_uuid, ESP_GATT_PERM_READ, sizeof(uint16_t), sizeof(INFO_SERV_uuid), (uint8_t *)&INFO_SERV_uuid}},
...
 };

static void info_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param)
{
    switch (event) {
        case ESP_GATTS_REG_EVT:{
            esp_err_t set_dev_name_ret = esp_ble_gap_set_device_name(DEVICE_NAME);

            //config adv data
            esp_err_t ret = esp_ble_gap_config_adv_data(&adv_data);
            adv_config_done |= ADV_CONFIG_FLAG;

            //config scan response data
            ret = esp_ble_gap_config_adv_data(&scan_rsp_data);
            adv_config_done |= SCAN_RSP_CONFIG_FLAG;

            esp_err_t create_attr_ret = esp_ble_gatts_create_attr_tab(info_serv_gatt_db, gatts_if, INFO_NB, SVC_INST_ID);
        }
            break;
        case ESP_GATTS_READ_EVT:

... case ESP_GATTS_EXEC_WRITE_EVT: ...
case ESP_GATTS_MTU_EVT: ESP_LOGI(TAG_IBLUETOOTH, "ESP_GATTS_MTU_EVT, MTU %d", param->mtu.mtu); break;

        case ESP_GATTS_CONF_EVT:
            ESP_LOGI(TAG_IBLUETOOTH, "ESP_GATTS_CONF_EVT, status = %d", param->conf.status);
            break;

        case ESP_GATTS_START_EVT:
            ESP_LOGI(TAG_IBLUETOOTH, "SERVICE_START_EVT, status %d, service_handle %d", param->start.status, param->start.service_handle);
            break;

        case ESP_GATTS_CONNECT_EVT:
            ESP_LOGI(TAG_IBLUETOOTH, "ESP_GATTS_CONNECT_EVT, conn_id = %d", param->connect.conn_id);
            esp_log_buffer_hex(TAG_IBLUETOOTH, param->connect.remote_bda, 6);
            esp_ble_conn_update_params_t conn_params = {0};
            memcpy(conn_params.bda, param->connect.remote_bda, sizeof(esp_bd_addr_t));
            /* For the IOS system, please reference the apple official documents about the ble connection parameters restrictions. */
            conn_params.latency = 0;
            conn_params.max_int = 0x20;    // max_int = 0x20*1.25ms = 40ms
            conn_params.min_int = 0x10;    // min_int = 0x10*1.25ms = 20ms
            conn_params.timeout = 400;    // timeout = 400*10ms = 4000ms
            //start sent the update connection parameters to the peer device.
            esp_ble_gap_update_conn_params(&conn_params);
            break;
        case ESP_GATTS_DISCONNECT_EVT:
            ESP_LOGI(TAG_IBLUETOOTH, "ESP_GATTS_DISCONNECT_EVT, reason = %d", param->disconnect.reason);
            esp_ble_gap_start_advertising(&adv_params);
            break;

        case ESP_GATTS_CREAT_ATTR_TAB_EVT:
            if (param->add_attr_tab.status != ESP_GATT_OK){
                ESP_LOGE(TAG_IBLUETOOTH, "create attribute table failed, error code=0x%x", param->add_attr_tab.status);
            }
            else if (param->add_attr_tab.num_handle != INFO_NB){
                ESP_LOGE(TAG_IBLUETOOTH, "create attribute table abnormally, num_handle (%d) doesn't equal to HRS_IDX_NB(%d)", param->add_attr_tab.num_handle, INFO_NB);
            }
            else {
                ESP_LOGI(TAG_IBLUETOOTH, "create attribute table successfully, the number handle = %d\n",param->add_attr_tab.num_handle);
                memcpy(info_handle_table, param->add_attr_tab.handles, sizeof(info_handle_table));
                esp_ble_gatts_start_service(info_handle_table[INFO_SERV]);
            }
            break;

        case ESP_GATTS_STOP_EVT:
        case ESP_GATTS_OPEN_EVT:
        case ESP_GATTS_CANCEL_OPEN_EVT:
        case ESP_GATTS_CLOSE_EVT:
        case ESP_GATTS_LISTEN_EVT:
        case ESP_GATTS_CONGEST_EVT:
        case ESP_GATTS_UNREG_EVT:
        case ESP_GATTS_DELETE_EVT:
        default:
            break;
    }
}

I have a feeling I'm doing something pretty glaringly wrong... But any feedback on the process to do this, examples or suggestions would be much appreciated!


Solution

  • Sorry to anyone that has also been searching for an answer to this. I've setup a GitHub repo where I've modularized the ESP-IDF GATTS Service Table example. This project breaks the ESP-IDF Bluetooth into an Interface, GAP, GATT Server, and services. The nice thing about it is that the services are broken into separate files so it is quick and easy to add new services based on the template provided. It is still a rough work in progress, but hopefully it will help anyone who is confused by the esp-idf bluetooth examples.

    https://github.com/eagi223/esp-idf_Bluetooth_Multi-Service