espressif / esp-idf

Espressif IoT Development Framework. Official development framework for Espressif SoCs.
Apache License 2.0
13.76k stars 7.3k forks source link

Bluetooth GATT specification and implementation correspondence (IDFGH-10671) #11900

Closed cwahn closed 1 year ago

cwahn commented 1 year ago

Answers checklist.

General issue report

This might be because I am not familiar with the ESP-IDF Bluetooth stack, but it seems the relationship with the GATT specification concept and implementation are somewhat vague and unspecified.

I have reviewed the examples and guides in /esp-idf/examples/bluetooth/. And could not figure out the clear relationship between BLE GATT specification and implementation.

GATT has concepts of;

In implementation

I guess a profile table is to support multiple profiles, so basically a list of profiles. A profile instance consists of a callback function, attributes, app id, gatt_if, conn_id, which apparently represent a profile.

However, it is not clear what is representing a service. It seems like esp_ble_gatts_create_attr_tab could be used instead of esp_ble_gatts_create_service and vice versa. This makes the attribute table corresponds to "a" service. However ,when creating the table the example refersto an app_id, which corresponds to an application of profile. This make the list of attributes meant to represent a profile or multiple services. However, can't find such an example.

So my question is what is the idiomatic way to represent a service in ESP-IDF Bluetooth stack?

Is it a list of attributes that should be used for a service? In this case what happens to the secondary, and includes the service of GATT specification? They should major and auxiliary functionality of a "device" per SIG specification. So are they should be in different lists in the same profile(device)? or one. How should they register? Is only the primary handle or all the service handles?

Otherwise, there is no such structure in ESP-IDF Bluetooth stack and a list of attributes should be used to represent a profile or multiple services. If this is the case, what should be the construction of the list? (svc decls, chars... or svc decl, chars..., svc decl, chars... ) Also, if this is the case, how do the services should be registered???

esp-zhp commented 1 year ago

1- A profile can correspond to one or more services, for example, in the example "esp-idf/examples/bluetooth/bluedroid/ble/gatt_server," one profile corresponds to two services.

2- ESP-IDF supports two methods for creating services:

cwahn commented 1 year ago

@zhp0406 Thank you for the reply. I have checked out both of the examples and they still don't give a answer.

  1. With this approach, there is no idiomatic form to specify a profile, but it is implement-specific, right?
  2. You said esp_ble_gatts_create_attr_tab is for a service. Is that mean, if one goes with 2nd approach of multiple services, one should call multiple esp_ble_gatts_create_attr_tab at an ESP_GATTS_REG_EVT event and start the corresponding service on ESP_GATTS_CREAT_ATTR_TAB_EVT??
esp-zhp commented 1 year ago

@cwahn you are right,If you want to create multiple services, you need to call esp_ble_gatts_create_attr_tab and esp_ble_gatts_start_service multiple times.

esp-zhp commented 1 year ago

Let me explain again,

A Profile includes one or more Services, and each Service contains one or more Characteristics.

For example, if we have a health monitoring device, it can support two Profiles: "Health Status" and "Heart Rate Monitoring." Each Profile includes different Services; for instance, the "Health Status" Profile may contain two Services, "Step Count" and "Sleep Quality," while the "Heart Rate Monitoring" Profile may include two Services, "Heart Rate Measurement" and "Sensor Location."

In practical use, the client only discovers some services provided by the server. It is up to the client to determine which profile a specific service belongs to.

kitty7c6 commented 1 year ago

Hello! I just started to learn/try Ble on esp32s3 and documentation or examples really not enought to understand How to create Profile: [service1: char1, char2], [service2: char11, char22]

in the example "esp-idf/examples/bluetooth/bluedroid/ble/gatt_server," one profile corresponds to two services. Tutorial says two profiles with one service in each image

So, if I want to create two services in one profile by gatt service table, i need:

  1. create one profile
    #define PROFILE_NUM 1
    #define COMMON_PROFILE_APP_IDX 0
    static struct gatts_profile_inst gl_profile_tab[PROFILE_NUM] = {
    [COMMON_PROFILE_APP_IDX] = {
        .gatts_cb = gatts_common_event_handler,
        .gatts_if = ESP_GATT_IF_NONE,           
    },
    };
  2. create two enums, two tables and two **_gat_db[..]={...} for each service
    
    enum first_service_idx
    {
    IDX_SRV1_SERVICE,
    IDX_SRV1_CHAR1,
    IDX_SRV1_CHAR2,
    SRV1_NUMB_OF_IDX,
    };
    static const esp_gatts_attr_db_t first_gatt_db[SRV1_NUMB_OF_IDX] =
    {
    //Service 1
    [IDX_SRV1_SERVICE]        =
    {
        {ESP_GATT_AUTO_RSP}, 
        {
            ESP_UUID_LEN_16,                    
            (uint8_t *)&primary_service_uuid,   
            ESP_GATT_PERM_READ,                 
            sizeof(uint16_t),                   /
            sizeof(GATTS_COM_SERVICE_UUID),      
            (uint8_t *)&GATTS_COM_SERVICE_UUID   
        }
    },
    // Characteristic1 Declaration
    [IDX_SRV1_CHAR1]     =
    {
        {ESP_GATT_AUTO_RSP}, 
        {
            ESP_UUID_LEN_16, 
            (uint8_t *)&character_declaration_uuid,
            ESP_GATT_PERM_READ,
            CHAR_DECLARATION_SIZE,
            CHAR_DECLARATION_SIZE, 
            (uint8_t *)&char_prop_read_write_notify
        }
    },
    // Characteristic2 Declaration
    [IDX_SRV1_CHAR2]     =
    {
        {ESP_GATT_AUTO_RSP}, 
        {
            ESP_UUID_LEN_16, 
            (uint8_t *)&character_declaration_uuid,
            ESP_GATT_PERM_READ,
            CHAR_DECLARATION_SIZE,
            CHAR_DECLARATION_SIZE, 
            (uint8_t *)&char_prop_read_write_notify
        }
    }
    }

enum second_service_idx { IDX_SRV2_SERVICE, IDX_SRV2_CHAR11, IDX_SRV2_CHAR22, SRV2_NUMB_OF_IDX, }; static const esp_gatts_attr_db_t second_gatt_db[SRV2_NUMB_OF_IDX] = { //Service 2 [IDX_SRV2_SERVICE] = { {ESP_GATT_AUTO_RSP}, { ESP_UUID_LEN_16,
(uint8_t )&primary_service_uuid,
ESP_GATT_PERM_READ,
sizeof(uint16_t), / sizeof(GATTS_COM_SERVICE_UUID),
(uint8_t
)&GATTS_COM_SERVICE_UUID
} }, // Characteristic11 Declaration [IDX_SRV2_CHAR11] = { {ESP_GATT_AUTO_RSP}, { ESP_UUID_LEN_16, (uint8_t )&character_declaration_uuid, ESP_GATT_PERM_READ, CHAR_DECLARATION_SIZE, CHAR_DECLARATION_SIZE, (uint8_t )&char_prop_read_write_notify } }, // Characteristic2 Declaration [IDX_SRV2_CHAR22] = { {ESP_GATT_AUTO_RSP}, { ESP_UUID_LEN_16, (uint8_t )&character_declaration_uuid, ESP_GATT_PERM_READ, CHAR_DECLARATION_SIZE, CHAR_DECLARATION_SIZE, (uint8_t )&char_prop_read_write_notify } } } uint16_t first_handle_table[SRV1_NUMB_OF_IDX]; uint16_t second_handle_table[SRV2_NUMB_OF_IDX];

3. in event ESP_GATTS_REG_EVT: call twice  esp_ble_gatts_create_attr_tab

esp_err_t create_attr_ret = esp_ble_gatts_create_attr_tab(first_gatt_db, gatts_if, SRV1_NUMB_OF_IDX, 1); esp_err_t create_attr_ret = esp_ble_gatts_create_attr_tab(second_gatt_db, gatts_if, SRV2_NUMB_OF_IDX, 2);

4. in case ESP_GATTS_CREAT_ATTR_TAB_EVT: call twice  esp_ble_gatts_start_service

memcpy(first_handle_table, param->add_attr_tab.handles, sizeof(first_handle_table)); esp_ble_gatts_start_service(first_handle_table[IDX_SRV1_SERVICE]); memcpy(second_handle_table, param->add_attr_tab.handles, sizeof(second_handle_table)); esp_ble_gatts_start_service(second_handle_table[IDX_SRV2_SERVICE]);


4. In other events check both services

That's right?
esp-zhp commented 1 year ago

@kitty7c6 right,and the following approach would be better: in case ESP_GATTS_CREAT_ATTR_TAB_EVT: call twice esp_ble_gatts_start_service

if(param->add_attr_tab.svc_inst_id == IDX_SRV1_SERVICE){
    //...
    //esp_ble_gatts_start_service IDX_SRV1_SERVICE
}
if(param->add_attr_tab.svc_inst_id == IDX_SRV2_SERVICE){
    //...
    //esp_ble_gatts_start_service IDX_SRV2_SERVICE
}
kitty7c6 commented 1 year ago

if(param->add_attr_tab.svc_inst_id == IDX_SRV1_SERVICE){ //... //esp_ble_gatts_start_service IDX_SRV1_SERVICE }

if I make two enums

enum first_service_idx
{
    IDX_SRV1_SERVICE, // here will be default 0
    IDX_SRV1_CHAR1,   // and 1
    IDX_SRV1_CHAR2,   // 2
    SRV1_NUMB_OF_IDX, // 3
}; 

and

enum second_service_idx
{
    IDX_SRV2_SERVICE, // here will be default 0 too!
    IDX_SRV2_CHAR11,
    IDX_SRV2_CHAR22,
    SRV2_NUMB_OF_IDX,
};

the code

f(param->add_attr_tab.svc_inst_id == IDX_SRV1_SERVICE){
    //...
    //esp_ble_gatts_start_service IDX_SRV1_SERVICE
}
if(param->add_attr_tab.svc_inst_id == IDX_SRV2_SERVICE){
    //...
    //esp_ble_gatts_start_service IDX_SRV2_SERVICE
}

will be similar

f(param->add_attr_tab.svc_inst_id == 0){
    //...
    //esp_ble_gatts_start_service IDX_SRV1_SERVICE
}
if(param->add_attr_tab.svc_inst_id == 0){
    //...
    //esp_ble_gatts_start_service IDX_SRV2_SERVICE
}

May be, if I call

esp_err_t create_attr_ret = esp_ble_gatts_create_attr_tab(first_gatt_db, gatts_if, SRV1_NUMB_OF_IDX, 1); - here id of srv1=1
esp_err_t create_attr_ret = esp_ble_gatts_create_attr_tab(second_gatt_db, gatts_if, SRV2_NUMB_OF_IDX, 2);  - here id of srv1=2

will be better:

#define srv1_ID 1
#define srv2_ID 2
esp_err_t create_attr_ret = esp_ble_gatts_create_attr_tab(first_gatt_db, gatts_if, SRV1_NUMB_OF_IDX, srv1_ID ); 
esp_err_t create_attr_ret = esp_ble_gatts_create_attr_tab(second_gatt_db, gatts_if, SRV2_NUMB_OF_IDX, srv2_ID );  

f(param->add_attr_tab.svc_inst_id == srv1_ID ){
    //...
    //esp_ble_gatts_start_service IDX_SRV1_SERVICE
}
if(param->add_attr_tab.svc_inst_id == srv2_ID ){
    //...
    //esp_ble_gatts_start_service IDX_SRV2_SERVICE
}
Alvin1Zhang commented 1 year ago

Thanks for reporting, feel free to reopen.