espressif / esp-idf

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

Bluedroid gattserver callback issue (IDFGH-8854) #10278

Closed nouwon closed 6 months ago

nouwon commented 1 year ago

Answers checklist.

General issue report

How do I capture ESP_GATTS_DISCONNECT_EVT after client connection is closed?I am using esp-idf ver 4.4.3. Currently an android client performs connection,sends data,receives data successfully but when the android client disconnects gatt server call back event ESP_GATTS_DISCONNECT_EVT is never triggered. Similar problems have been reported by other users but there is no satisfying solution proposal for this issue? Any help will be highly apprecited. Thanks

xulongzhuang commented 1 year ago

@nouwon Can you provide a method and more information to help reproduce this issue? Does your usage scenario use WIFI and high throughput to transfer data?

BLE link layer disconnection does not report ESP_GATTS_DISCONNECT_EVT event is rarely encountered.

I use the Android BLE app to connect to the esp32 running the example of gatt_server_service_table. When the connection is disconnected, the disconnection event will be reported every time in gatts_profile_event_handler

nouwon commented 1 year ago

Thank you very much for your response xulogzhuang. Following are relevant initialization and registration functions. I can provide further code if nothing wrong is present here.

Project brief: 1-First I walk through an initialization sequence in my main.c file with app_state_handler() function call.

static void app_state_handler(){

    /* Check the application's current state. */
    switch (appData.state)
    {
            /* Application's initial state. */
        case APP_STATE_INIT:
        {
            app_init();
            ESP_LOGI(SYS_TAG, "%s System is initialized: %s\n", __func__, esp_err_to_name(ESP_OK));
            break;
        }
        case APP_STATE_TASKS_INIT:
        {
            tasks_init();
            break;
        }
        case APP_STATE_ERROR:
        {
            ESP_LOGE(SYS_TAG, "%s System encountered error!: %s\n", __func__, esp_err_to_name(ESP_FAIL));
            break;
        }
        /* The default state should never be executed. */
        default:
        {

            appData.state = APP_STATE_INIT;

            break;
        }
    }
}

2-app_init function call initializes ble

static void app_init(){

    if(ble_init()){
         ESP_LOGI(SYS_TAG, "%s ble initialized !: %s\n", __func__, esp_err_to_name(ESP_OK));
        appData.state=APP_STATE_TASKS_INIT;
        send_system_event(APP_STATE_CHANGED);
    }
    else
         ESP_LOGE(SYS_TAG, "%s BLE init failed: %s\n", __func__, esp_err_to_name(ESP_FAIL));

    }
}
here is my ble initializaton method copied as it is given in the examples.
bool ble_init(void)
{
esp_err_t ret;
esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT();
// Initialize NVS
ret = nvs_flash_init();
if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND)
{
    ESP_ERROR_CHECK(nvs_flash_erase());
    ret = nvs_flash_init();
}
ESP_ERROR_CHECK(ret);

ESP_ERROR_CHECK(esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT));

ret = esp_bt_controller_init(&bt_cfg);
if (ret)
{
    ESP_LOGE(GATTS_TABLE_TAG, "%s esp bt controller init failed: %s\n", __func__, esp_err_to_name(ret));
    return false;
}

ret = esp_bt_controller_enable(ESP_BT_MODE_BLE);

if (ret)
{
    ESP_LOGE(GATTS_TABLE_TAG, "%s esp  bt controller enable failed: %s\n", __func__, esp_err_to_name(ret));
    return false;
}

ret = esp_bluedroid_init();
if (ret)
{
    ESP_LOGE(GATTS_TABLE_TAG, "%s init bluetooth failed: %s\n", __func__, esp_err_to_name(ret));
    return false;
}
ret = esp_bluedroid_enable();
if (ret)
{
    ESP_LOGE(GATTS_TABLE_TAG, "%s enable bluetooth failed: %s\n", __func__, esp_err_to_name(ret));
    return false;
}

esp_ble_gatts_register_callback(gatts_event_handler);
esp_ble_gap_register_callback(gap_event_handler);
esp_ble_gatts_app_register(ESP_SPP_APP_ID);

return true;
}

3- If ble initialization succeeds I proceed to initiliaze freeRTOS tasks with tasks_init() function call.

static void tasks_init(){

    spi_controller_init();
    obdmgr_tasks_init();
    msgmgr_task_init();
    spp_task_init();
    cmdmgr_task_init();

}

above tasks are standard freeRtos tasks each of which perform well without any problem.

4- spp_task is straight forward ble_spp_server_demo.c code void spp_task_init(void) { spp_uart_init();

    // moved to cmdmgr
    /* cmd_cmd_queue = xQueueCreate(10, sizeof(uint32_t));
     xTaskCreate(spp_cmd_task, "spp_cmd_task", 2048, NULL, 10, NULL);*/
}

5- callback functions as given by examples is used with minor changes .(changes are made to force fire ESP_GATTS_DISCONNECT_EVT and are not required as per project specifications.changes made no effect.)

static void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param)
{
    esp_err_t err;

    switch (event)
    {
    case ESP_GAP_BLE_ADV_DATA_RAW_SET_COMPLETE_EVT:
        esp_ble_gap_start_advertising(&spp_adv_params);
        break;
    case ESP_GAP_BLE_ADV_START_COMPLETE_EVT:
        // advertising start complete event to indicate advertising start successfully or failed

        if ((err = param->adv_start_cmpl.status) != ESP_BT_STATUS_SUCCESS)
        {
            ESP_LOGE(GATTS_TABLE_TAG, "Advertising start failed: %s\n", esp_err_to_name(err));
        }
        break;
    default:
        break;
    }
}
//-----------------------------------------------------------------------------------------------------------------------------
static void gatts_profile_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param)
{
    esp_ble_gatts_cb_param_t *p_data = (esp_ble_gatts_cb_param_t *)param;
    uint8_t res = 0xff;

    ESP_LOGI(GATTS_TABLE_TAG, "event = %x\n", event);
    switch (event)
    {
    case ESP_GATTS_REG_EVT:
        ESP_LOGI(GATTS_TABLE_TAG, "%s %d\n", __func__, __LINE__);
        esp_ble_gap_set_device_name(SAMPLE_DEVICE_NAME);

        ESP_LOGI(GATTS_TABLE_TAG, "%s %d\n", __func__, __LINE__);
        esp_ble_gap_config_adv_data_raw((uint8_t *)spp_adv_data, sizeof(spp_adv_data));

        ESP_LOGI(GATTS_TABLE_TAG, "%s %d\n", __func__, __LINE__);
        esp_ble_gatts_create_attr_tab(spp_gatt_db, gatts_if, SPP_IDX_NB, SPP_SVC_INST_ID);
        break;
    case ESP_GATTS_READ_EVT:
        res = find_char_and_desr_index(p_data->read.handle);
        if (res == SPP_IDX_SPP_STATUS_VAL)
        {
            // TODO:client read the status characteristic
        }
        break;
    case ESP_GATTS_WRITE_EVT:
    {
        res = find_char_and_desr_index(p_data->write.handle);
        if (p_data->write.is_prep == false)
        {
            ESP_LOGI(GATTS_TABLE_TAG, "ESP_GATTS_WRITE_EVT : handle = %d\n", res);
            if (res == SPP_IDX_SPP_COMMAND_VAL)
            {
                uint8_t *spp_cmd_buff = NULL;
                spp_cmd_buff = (uint8_t *)malloc((spp_mtu_size - 3) * sizeof(uint8_t));
                if (spp_cmd_buff == NULL)
                {
                    ESP_LOGE(GATTS_TABLE_TAG, "%s malloc failed\n", __func__);
                    break;
                }
                memset(spp_cmd_buff, 0x0, (spp_mtu_size - 3));
                memcpy(spp_cmd_buff, p_data->write.value, p_data->write.len);
                xQueueSend(cmd_cmd_queue, &spp_cmd_buff, 10 / portTICK_PERIOD_MS);
            }
            else if (res == SPP_IDX_SPP_DATA_NTF_CFG)
            {
                if ((p_data->write.len == 2) && (p_data->write.value[0] == 0x01) && (p_data->write.value[1] == 0x00))
                {
                    enable_data_ntf = true;
                }
                else if ((p_data->write.len == 2) && (p_data->write.value[0] == 0x00) && (p_data->write.value[1] == 0x00))
                {
                    enable_data_ntf = false;
                }
            }

            else if (res == SPP_IDX_SPP_DATA_RECV_VAL)
            {
#ifdef SPP_DEBUG_MODE
                esp_log_buffer_char(GATTS_TABLE_TAG, (char *)(p_data->write.value), p_data->write.len);
#else

                uart_write_bytes(UART_NUM_0, (char *)(p_data->write.value), p_data->write.len);
                send_message_received_event((uint8_t *)(p_data->write.value), p_data->write.len);
#endif
            }
            else
            {
                // TODO:
                ESP_LOGI(GATTS_TABLE_TAG, "unhandled case");
            }
        }
        else if ((p_data->write.is_prep == true) && (res == SPP_IDX_SPP_DATA_RECV_VAL))
        {
            ESP_LOGI(GATTS_TABLE_TAG, "ESP_GATTS_PREP_WRITE_EVT : handle = %d\n", res);
            store_wr_buffer(p_data);
        }
        break;
    }
    case ESP_GATTS_EXEC_WRITE_EVT:
    {
        ESP_LOGI(GATTS_TABLE_TAG, "ESP_GATTS_EXEC_WRITE_EVT\n");
        if (p_data->exec_write.exec_write_flag)
        {
            print_write_buffer();
            free_write_buffer();
        }
        break;
    }
    case ESP_GATTS_MTU_EVT:
        spp_mtu_size = p_data->mtu.mtu;
        break;
    case ESP_GATTS_CONF_EVT:
        break;
    case ESP_GATTS_UNREG_EVT:
        break;
    case ESP_GATTS_DELETE_EVT:
        break;
    case ESP_GATTS_START_EVT:
        break;
    case ESP_GATTS_STOP_EVT:
        break;
    case ESP_GATTS_CONNECT_EVT:
        spp_conn_id = p_data->connect.conn_id;
        spp_gatts_if = gatts_if;
        is_connected = true;
        memcpy(&spp_remote_bda, &p_data->connect.remote_bda, sizeof(esp_bd_addr_t));

        esp_ble_conn_update_params_t conn_params = {0};
        memcpy(&conn_params.bda, &spp_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
        ESP_LOGI(GATTS_TABLE_TAG, "ESP_GATTS_CONNECT_EVT, conn_id %d, remote %02x:%02x:%02x:%02x:%02x:%02x:",
                 param->connect.conn_id,
                 param->connect.remote_bda[0], param->connect.remote_bda[1], param->connect.remote_bda[2],
                 param->connect.remote_bda[3], param->connect.remote_bda[4], param->connect.remote_bda[5]);
        spp_profile_tab[SPP_PROFILE_APP_IDX].conn_id = param->connect.conn_id;
        //start sending the update connection parameters to the peer device.
        esp_ble_gap_update_conn_params(&conn_params);

        break;
    case ESP_GATTS_DISCONNECT_EVT:
        ESP_LOGI(GATTS_TABLE_TAG, "ESP_GATTS_DISCONNECT_EVT, disconnect reason 0x%x", param->disconnect.reason);
        is_connected = false;
        enable_data_ntf = false;
        esp_ble_gap_start_advertising(&spp_adv_params);
        break;
    case ESP_GATTS_OPEN_EVT:
        break;
    case ESP_GATTS_CANCEL_OPEN_EVT:
        break;
    case ESP_GATTS_CLOSE_EVT:
         ESP_LOGI(GATTS_TABLE_TAG, "ESP_GATTS_CLOSE_EVT, disconnect reason 0x%x", param->disconnect.reason);
        break;
    case ESP_GATTS_LISTEN_EVT:
        break;
    case ESP_GATTS_CONGEST_EVT:
        break;
    case ESP_GATTS_CREAT_ATTR_TAB_EVT:
    {
        ESP_LOGI(GATTS_TABLE_TAG, "The number handle =%x\n", param->add_attr_tab.num_handle);
        if (param->add_attr_tab.status != ESP_GATT_OK)
        {
            ESP_LOGE(GATTS_TABLE_TAG, "Create attribute table failed, error code=0x%x", param->add_attr_tab.status);
        }
        else if (param->add_attr_tab.num_handle != SPP_IDX_NB)
        {
            ESP_LOGE(GATTS_TABLE_TAG, "Create attribute table abnormally, num_handle (%d) doesn't equal to HRS_IDX_NB(%d)", param->add_attr_tab.num_handle, SPP_IDX_NB);
        }
        else
        {
            memcpy(spp_handle_table, param->add_attr_tab.handles, sizeof(spp_handle_table));
            esp_ble_gatts_start_service(spp_handle_table[SPP_IDX_SVC]);
        }
        break;
    }
    default:
        break;
    }
}
//----------------------------------------------------------------------------------------------------------------------------------------------
static void gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param)
{
    // ESP_LOGI(GATTS_TABLE_TAG, "event = %x\n", event);
    /* 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)
        {
            spp_profile_tab[SPP_PROFILE_APP_IDX].gatts_if = gatts_if;
        }
        else
        {
            ESP_LOGI(GATTS_TABLE_TAG, "Reg app failed, app_id %04x, status %d\n", param->reg.app_id, param->reg.status);
            return;
        }
    }

    do
    {
        int idx;
        for (idx = 0; idx < SPP_PROFILE_NUM; idx++)
        {
            if (gatts_if == ESP_GATT_IF_NONE || /* ESP_GATT_IF_NONE, not specify a certain gatt_if, need to call every profile cb function */
                gatts_if == spp_profile_tab[idx].gatts_if)
            {
                if (spp_profile_tab[idx].gatts_cb)
                {
                    spp_profile_tab[idx].gatts_cb(event, gatts_if, param);
                }
            }
        }
    } while (0);
}
xulongzhuang commented 1 year ago

The above code does not look much different from ble_spp_server. Since the above code is not complete, I used ble_spp_server to do the following tests:

  1. ble_spp_server_demo.h opens the SUPPORT_HEARTBEAT macro
  2. In case ESP_GATTS_DISCONNECT_EVT: add and print as follows ESP_LOGI(GATTS_TABLE_TAG, "ESP_GATTS_DISCONNECT_EVT, disconnect reason 0x%x", param->disconnect.reason);
  3. The BLE app on the mobile phone scans and connects to the BLE_SPP_SERVER device
  4. After waiting more than ten seconds, the BLE_SPP_SERVER device will call esp_ble_gap_disconnect(spp_remote_bda) to actively disconnect

2022-12-01_20-29 As can be seen in the figure above, there is a disconnection event

If your code behavior is inconsistent with the above process, please tell me more detailed information, such as the Android client used, the reproduced demo and process, etc.

nouwon commented 1 year ago

I am migrating to esp-idf ver 5.0 current version is 4.4.3. As soon as I make it run with the new esp-idf version I will comment about the result. Thank you very much for your kind efforts to resolve the issue. May I learn your esp-idf version?

nouwon commented 1 year ago

@xulongzhuang Now I use idf version 5.0. here is a log output: I (239365) GATTS_ARADS: ESP_GATTS_CONNECT_EVT, conn_id 0, remote 76:d4:e4:3b:71:b5: I (239955) GATTS_ARADS: update connection params device 76:d4:e4:3b:71:b5 ,status = 0, min_int = 16, max_int = 16,conn_int = 16,latency = 0, timeout = 400 I (240185) GATTS_ARADS: update connection params device 76:d4:e4:3b:71:b5 ,status = 0, min_int = 0, max_int = 0,conn_int = 6,latency = 0, timeout = 500 I (240425) GATTS_ARADS: event = 4

I (240505) GATTS_ARADS: update connection params device 76:d4:e4:3b:71:b5 ,status = 0, min_int = 0, max_int = 0,conn_int = 16,latency = 0, timeout = 400 I (240785) GATTS_ARADS: update connection params device 76:d4:e4:3b:71:b5 ,status = 0, min_int = 0, max_int = 0,conn_int = 9,latency = 0, timeout = 2000 I (240995) GATTS_ARADS: event = 2

weid thing is multiple update params occur for the same device adress.Is this related to my issue? On the client side I use both my custom app and nRF connect with the same result.

nouwon commented 1 year ago

@xulongzhuang Problem seems to be a hardware problem.I could not figure out why but I changed the board and now the callback event is fired properly. Thank you for the help.

Alvin1Zhang commented 6 months ago

Thanks for reporting, feel free to reopen.