From 0a909762377f2612dcf8b25681a141d0c1c598fd Mon Sep 17 00:00:00 2001 From: Mayank Tyagi Date: Tue, 17 Mar 2026 15:43:11 +0530 Subject: [PATCH 01/13] feat(nimble): ADD BLE HCI logging support for ESP Insights --- components/bt/Kconfig | 6 ++ components/bt/common/CMakeLists.txt | 2 +- components/bt/common/hci_log/bt_hci_log.c | 10 +++ .../bt/common/hci_log/bt_hci_log_insights.c | 90 +++++++++++++++++++ .../hci_log/include/hci_log/bt_hci_log.h | 13 ++- .../bt/common/include/bt_ble_insights.h | 21 +++++ components/bt/common/include/bt_common.h | 17 +++- components/bt/common/include/bt_user_config.h | 10 ++- .../bt/host/bluedroid/api/esp_bluedroid_hci.c | 9 +- components/bt/host/bluedroid/hci/hci_hal_h4.c | 9 +- .../host/nimble/esp-hci/src/esp_nimble_hci.c | 24 +++-- components/bt/host/nimble/nimble | 2 +- .../host/nimble/port/include/esp_nimble_cfg.h | 8 ++ 13 files changed, 204 insertions(+), 17 deletions(-) create mode 100644 components/bt/common/hci_log/bt_hci_log_insights.c create mode 100644 components/bt/common/include/bt_ble_insights.h diff --git a/components/bt/Kconfig b/components/bt/Kconfig index 05f57d79e31..caf7d5440c4 100644 --- a/components/bt/Kconfig +++ b/components/bt/Kconfig @@ -109,6 +109,12 @@ menu "Bluetooth" This option is to configure the buffer size of the hci adv report cache in hci debug mode. This is a ring buffer, the new data will overwrite the oldest data if the buffer is full. + config BT_HCI_LOG_INSIGHTS_ENABLE + depends on BT_HCI_LOG_DEBUG_EN + bool "Enable Insights for HCI LOGS BT Stack" + help + Enable this to allow the BT stack to send diagnostic events. + endmenu menuconfig BLE_MESH diff --git a/components/bt/common/CMakeLists.txt b/components/bt/common/CMakeLists.txt index 95c2fad76b2..062a4498da8 100644 --- a/components/bt/common/CMakeLists.txt +++ b/components/bt/common/CMakeLists.txt @@ -94,7 +94,7 @@ list(APPEND bt_common_srcs "${CMAKE_CURRENT_LIST_DIR}/api/esp_blufi_api.c" "${CMAKE_CURRENT_LIST_DIR}/hci_log/bt_hci_log.c" "${CMAKE_CURRENT_LIST_DIR}/btc/core/btc_manage.c" - # "${CMAKE_CURRENT_LIST_DIR}/hci_log/bt_hci_log_insights.c" + "${CMAKE_CURRENT_LIST_DIR}/hci_log/bt_hci_log_insights.c" "${CMAKE_CURRENT_LIST_DIR}/btc/core/btc_task.c" "${CMAKE_CURRENT_LIST_DIR}/btc/profile/esp/blufi/blufi_prf.c" "${CMAKE_CURRENT_LIST_DIR}/btc/profile/esp/blufi/blufi_protocol.c" diff --git a/components/bt/common/hci_log/bt_hci_log.c b/components/bt/common/hci_log/bt_hci_log.c index 5840adab856..317d4b1718c 100644 --- a/components/bt/common/hci_log/bt_hci_log.c +++ b/components/bt/common/hci_log/bt_hci_log.c @@ -40,6 +40,16 @@ static const char s_hex_to_char_mapping[16] = { static bt_hci_log_t g_bt_hci_log_data_ctl = {0}; static bt_hci_log_t g_bt_hci_log_adv_ctl = {0}; +uint8_t bt_hci_log_h4_type_to_data_type(uint8_t h4_type) +{ + switch (h4_type) { + case 0x05: + return HCI_LOG_DATA_TYPE_ISO_DATA; + default: + return h4_type; + } +} + esp_err_t bt_hci_log_init(void) { uint8_t *g_bt_hci_log_data_buffer = NULL; diff --git a/components/bt/common/hci_log/bt_hci_log_insights.c b/components/bt/common/hci_log/bt_hci_log_insights.c new file mode 100644 index 00000000000..8ab832efa4f --- /dev/null +++ b/components/bt/common/hci_log/bt_hci_log_insights.c @@ -0,0 +1,90 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include + +#include "freertos/FreeRTOS.h" +#include "freertos/portmacro.h" + +#include "esp_timer.h" +#include "bt_ble_insights.h" +#include "bt_common.h" +#include "hci_log/bt_hci_log.h" + +#if (BT_HCI_LOG_INCLUDED == TRUE) && BT_HCI_INSIGHTS_INCLUDED +static uint8_t s_hci_log_seq_num = 0; +static portMUX_TYPE s_hci_log_mux = portMUX_INITIALIZER_UNLOCKED; + +#define HCI_LOG_INSIGHTS_LINE_SIZE 128U +#define HCI_LOG_INSIGHTS_TS_LEN 8U + +static const char *bt_hci_log_insights_label(uint8_t data_type) +{ + switch (data_type) { + case HCI_LOG_DATA_TYPE_COMMAND: + return "C"; + case HCI_LOG_DATA_TYPE_H2C_ACL: + return "H"; + case HCI_LOG_DATA_TYPE_SCO: + return "S"; + case HCI_LOG_DATA_TYPE_EVENT: + return "E"; + case HCI_LOG_DATA_TYPE_ADV: + return "ADV"; + case HCI_LOG_DATA_TYPE_C2H_ACL: + return "D"; + case HCI_LOG_DATA_TYPE_ISO_DATA: + return "I"; + default: + return NULL; + } +} + +void bt_hci_log_record_insights(uint8_t data_type, const uint8_t *data, uint16_t data_len) +{ + const char *label = bt_hci_log_insights_label(data_type); + char line[HCI_LOG_INSIGHTS_LINE_SIZE]; + uint8_t ts_bytes[HCI_LOG_INSIGHTS_TS_LEN]; + uint64_t timestamp; + uint8_t seq_num; + int offset; + + if (!BT_BLE_INSIGHTS_AVAILABLE || label == NULL || data == NULL || data_len == 0) { + return; + } + + portENTER_CRITICAL(&s_hci_log_mux); + seq_num = ++s_hci_log_seq_num; + portEXIT_CRITICAL(&s_hci_log_mux); + + timestamp = esp_timer_get_time(); + memcpy(ts_bytes, ×tamp, sizeof(ts_bytes)); + + offset = snprintf(line, sizeof(line), "%02x %s:", (unsigned int)seq_num, label); + if (offset < 0 || (size_t)offset >= sizeof(line) - 1) { + return; + } + + for (size_t i = 0; i < sizeof(ts_bytes) && offset <= (int)sizeof(line) - 3; i++) { + offset += snprintf(&line[offset], sizeof(line) - offset, "%02x", ts_bytes[i]); + } + + if (offset < 0 || (size_t)offset >= sizeof(line) - 1) { + return; + } + + line[offset++] = ' '; + line[offset] = '\0'; + + for (uint16_t i = 0; i < data_len && offset <= (int)sizeof(line) - 3; i++) { + offset += snprintf(&line[offset], sizeof(line) - offset, "%02X", data[i]); + } + + ble_insights_log(line); +} +#endif diff --git a/components/bt/common/hci_log/include/hci_log/bt_hci_log.h b/components/bt/common/hci_log/include/hci_log/bt_hci_log.h index 4646862194c..8174bc06c95 100644 --- a/components/bt/common/hci_log/include/hci_log/bt_hci_log.h +++ b/components/bt/common/hci_log/include/hci_log/bt_hci_log.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2015-2024 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2015-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -102,6 +102,17 @@ esp_err_t bt_hci_log_record_hci_data(uint8_t data_type, uint8_t *data, uint16_t */ esp_err_t bt_hci_log_record_hci_adv(uint8_t data_type, uint8_t *data, uint8_t data_len); +/** + * + * @brief Convert HCI H4 packet type to HCI log data type. + * + * @param h4_type : HCI H4 packet type byte + * + * @return corresponding HCI log data type + * + */ +uint8_t bt_hci_log_h4_type_to_data_type(uint8_t h4_type); + #ifdef __cplusplus } #endif diff --git a/components/bt/common/include/bt_ble_insights.h b/components/bt/common/include/bt_ble_insights.h new file mode 100644 index 00000000000..c433eed982e --- /dev/null +++ b/components/bt/common/include/bt_ble_insights.h @@ -0,0 +1,21 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef __BT_BLE_INSIGHTS_H__ +#define __BT_BLE_INSIGHTS_H__ + +#if __has_include("ble_insights.h") +#include "ble_insights.h" +#define BT_BLE_INSIGHTS_AVAILABLE 1 +#else +#define BT_BLE_INSIGHTS_AVAILABLE 0 +static inline void ble_insights_log(const char *log) +{ + (void)log; +} +#endif + +#endif /* __BT_BLE_INSIGHTS_H__ */ diff --git a/components/bt/common/include/bt_common.h b/components/bt/common/include/bt_common.h index 372ea68e7e9..343600f36e6 100644 --- a/components/bt/common/include/bt_common.h +++ b/components/bt/common/include/bt_common.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2015-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2015-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -100,6 +100,13 @@ #define BT_HCI_LOG_INCLUDED FALSE #endif +// HCI INSIGHTS LOG +#if UC_BT_HCI_LOG_INSIGHTS_ENABLE +#define BT_HCI_INSIGHTS_INCLUDED UC_BT_HCI_LOG_INSIGHTS_ENABLE +#else +#define BT_HCI_INSIGHTS_INCLUDED FALSE +#endif + // HCI LOG TO SPI #if UC_BT_BLE_LOG_SPI_OUT_HCI_ENABLED #define BT_BLE_LOG_SPI_OUT_HCI_ENABLED UC_BT_BLE_LOG_SPI_OUT_HCI_ENABLED @@ -117,10 +124,10 @@ #if UC_BT_HCI_LOG_DATA_BUFFER_SIZE #define HCI_LOG_DATA_BUFFER_SIZE UC_BT_HCI_LOG_DATA_BUFFER_SIZE #else -#define HCI_BUFFER_SIZE (5) +#define HCI_LOG_DATA_BUFFER_SIZE (5) #endif -#if UC_BT_HCI_ADV_BUFFER_SIZE +#if UC_BT_HCI_LOG_ADV_BUFFER_SIZE #define HCI_LOG_ADV_BUFFER_SIZE UC_BT_HCI_LOG_ADV_BUFFER_SIZE #else #define HCI_LOG_ADV_BUFFER_SIZE (5) @@ -277,4 +284,8 @@ typedef struct { #define BD_ADDR_LEN 6 /* Device address length */ typedef UINT8 BD_ADDR[BD_ADDR_LEN]; /* Device address */ +#if (BT_HCI_LOG_INCLUDED == TRUE) && BT_HCI_INSIGHTS_INCLUDED +void bt_hci_log_record_insights(uint8_t data_type, const uint8_t *data, uint16_t data_len); +#endif + #endif /* _BT_COMMON_H_ */ diff --git a/components/bt/common/include/bt_user_config.h b/components/bt/common/include/bt_user_config.h index 3ddc188f13b..bc8a41c77ec 100644 --- a/components/bt/common/include/bt_user_config.h +++ b/components/bt/common/include/bt_user_config.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2015-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2015-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -176,4 +176,12 @@ #define UC_BT_HCI_LOG_ADV_BUFFER_SIZE (5) #endif + +// HCI LOG INSIGHTS +#ifdef CONFIG_BT_HCI_LOG_INSIGHTS_ENABLE +#define UC_BT_HCI_LOG_INSIGHTS_ENABLE TRUE +#else +#define UC_BT_HCI_LOG_INSIGHTS_ENABLE FALSE +#endif + #endif /* __BT_USER_CONFIG_H__ */ diff --git a/components/bt/host/bluedroid/api/esp_bluedroid_hci.c b/components/bt/host/bluedroid/api/esp_bluedroid_hci.c index 5476f43409d..59c1ab268b1 100644 --- a/components/bt/host/bluedroid/api/esp_bluedroid_hci.c +++ b/components/bt/host/bluedroid/api/esp_bluedroid_hci.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2015-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2015-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -8,6 +8,7 @@ #include "esp_log.h" #include "esp_bluedroid_hci.h" #include "common/bt_target.h" +#include "bt_common.h" #include "hci/hci_trans_int.h" #if (BT_CONTROLLER_INCLUDED == TRUE) #include "esp_bt.h" @@ -69,7 +70,11 @@ void hci_host_send_packet(uint8_t *data, uint16_t len) { #if (BT_HCI_LOG_INCLUDED == TRUE) if (data != NULL && len > 1) { - bt_hci_log_record_hci_data(data[0], &data[1], (uint16_t)(len - 1)); + uint8_t data_type = bt_hci_log_h4_type_to_data_type(data[0]); + bt_hci_log_record_hci_data(data_type, &data[1], (uint16_t)(len - 1)); +#if BT_HCI_INSIGHTS_INCLUDED + bt_hci_log_record_insights(data_type, &data[1], (uint16_t)(len - 1)); +#endif } #endif #if CONFIG_BT_BLE_LOG_SPI_OUT_HCI_ENABLED diff --git a/components/bt/host/bluedroid/hci/hci_hal_h4.c b/components/bt/host/bluedroid/hci/hci_hal_h4.c index 5e7c116d545..abd7e75ccc5 100644 --- a/components/bt/host/bluedroid/hci/hci_hal_h4.c +++ b/components/bt/host/bluedroid/hci/hci_hal_h4.c @@ -34,6 +34,7 @@ #include "esp_bt.h" #endif #include "esp_bluedroid_hci.h" +#include "bt_common.h" #if (C2H_FLOW_CONTROL_INCLUDED == TRUE) #include "l2c_int.h" @@ -601,14 +602,20 @@ void bt_record_hci_data(uint8_t *data, uint16_t len) #endif // (BLE_50_FEATURE_SUPPORT == TRUE) )) { bt_hci_log_record_hci_adv(HCI_LOG_DATA_TYPE_ADV, &data[2], len - 2); +#if BT_HCI_INSIGHTS_INCLUDED + bt_hci_log_record_insights(HCI_LOG_DATA_TYPE_ADV, &data[2], len - 2); +#endif } else { uint8_t data_type; - if (data[0] == HCI_LOG_DATA_TYPE_ISO_DATA) { + if (data[0] == DATA_TYPE_ISO) { data_type = HCI_LOG_DATA_TYPE_ISO_DATA; } else { data_type = ((data[0] == 2) ? HCI_LOG_DATA_TYPE_C2H_ACL : data[0]); } bt_hci_log_record_hci_data(data_type, &data[1], len - 1); +#if BT_HCI_INSIGHTS_INCLUDED + bt_hci_log_record_insights(data_type, &data[1], len - 1); +#endif } #endif // (BT_HCI_LOG_INCLUDED == TRUE) } diff --git a/components/bt/host/nimble/esp-hci/src/esp_nimble_hci.c b/components/bt/host/nimble/esp-hci/src/esp_nimble_hci.c index 02065c08538..e81e9d481d9 100644 --- a/components/bt/host/nimble/esp-hci/src/esp_nimble_hci.c +++ b/components/bt/host/nimble/esp-hci/src/esp_nimble_hci.c @@ -78,7 +78,11 @@ void ble_hci_trans_cfg_hs(ble_hci_trans_rx_cmd_fn *cmd_cb, void esp_vhci_host_send_packet_wrapper(uint8_t *data, uint16_t len) { #if (BT_HCI_LOG_INCLUDED == TRUE) - bt_hci_log_record_hci_data(data[0], &data[1], len - 1); + uint8_t data_type = bt_hci_log_h4_type_to_data_type(data[0]); + bt_hci_log_record_hci_data(data_type, &data[1], len - 1); +#if BT_HCI_INSIGHTS_INCLUDED + bt_hci_log_record_insights(data_type, &data[1], len - 1); +#endif #endif #if CONFIG_BT_BLE_LOG_SPI_OUT_HCI_ENABLED ble_log_spi_out_hci_write(BLE_LOG_SPI_OUT_SOURCE_HCI_DOWNSTREAM, data, len); @@ -237,18 +241,24 @@ static void dummy_controller_rcv_pkt_ready(void) void bt_record_hci_data(uint8_t *data, uint16_t len) { #if (BT_HCI_LOG_INCLUDED == TRUE) - if ((data[0] == BLE_HCI_UART_H4_EVT) && (data[1] == BLE_HCI_EVCODE_LE_META) && ((data[3] == BLE_HCI_LE_SUBEV_ADV_RPT) || (data[3] == BLE_HCI_LE_SUBEV_DIRECT_ADV_RPT) + if (len < 2) { + return; + } + if ((len >= 4) && (data[0] == BLE_HCI_UART_H4_EVT) && (data[1] == BLE_HCI_EVCODE_LE_META) && ((data[3] == BLE_HCI_LE_SUBEV_ADV_RPT) || (data[3] == BLE_HCI_LE_SUBEV_DIRECT_ADV_RPT) || (data[3] == BLE_HCI_LE_SUBEV_EXT_ADV_RPT) || (data[3] == BLE_HCI_LE_SUBEV_PERIODIC_ADV_RPT))) { bt_hci_log_record_hci_adv(HCI_LOG_DATA_TYPE_ADV, &data[2], len - 2); +#if BT_HCI_INSIGHTS_INCLUDED + bt_hci_log_record_insights(HCI_LOG_DATA_TYPE_ADV, &data[2], len - 2); +#endif } else { uint8_t data_type; - if (data[0] == HCI_LOG_DATA_TYPE_ISO_DATA) { - data_type = HCI_LOG_DATA_TYPE_ISO_DATA; - } else { - data_type = ((data[0] == 2) ? HCI_LOG_DATA_TYPE_C2H_ACL : data[0]); - } + data_type = ((data[0] == 2) ? HCI_LOG_DATA_TYPE_C2H_ACL : bt_hci_log_h4_type_to_data_type(data[0])); bt_hci_log_record_hci_data(data_type, &data[1], len - 1); +#if BT_HCI_INSIGHTS_INCLUDED + bt_hci_log_record_insights(data_type, &data[1], len - 1); +#endif } + #endif // (BT_HCI_LOG_INCLUDED == TRUE) } diff --git a/components/bt/host/nimble/nimble b/components/bt/host/nimble/nimble index 5d8b70207a6..5045cb5eb88 160000 --- a/components/bt/host/nimble/nimble +++ b/components/bt/host/nimble/nimble @@ -1 +1 @@ -Subproject commit 5d8b70207a6324f45d9f548ff57ec3b154afccdd +Subproject commit 5045cb5eb885acb84d4ea86fbf9e5e0202cf0e84 diff --git a/components/bt/host/nimble/port/include/esp_nimble_cfg.h b/components/bt/host/nimble/port/include/esp_nimble_cfg.h index 54c854d66e2..a0f62f36aa2 100644 --- a/components/bt/host/nimble/port/include/esp_nimble_cfg.h +++ b/components/bt/host/nimble/port/include/esp_nimble_cfg.h @@ -2382,4 +2382,12 @@ #endif #endif +#ifndef MYNEWT_VAL_BT_NIMBLE_INSIGHTS_ENABLE +#ifdef CONFIG_BT_NIMBLE_INSIGHTS_ENABLE +#define MYNEWT_VAL_BT_NIMBLE_INSIGHTS_ENABLE CONFIG_BT_NIMBLE_INSIGHTS_ENABLE +#else +#define MYNEWT_VAL_BT_NIMBLE_INSIGHTS_ENABLE (0) +#endif +#endif + #endif From 842941b488c1a4f46f1423c0ed8d9fb43bc5aa06 Mon Sep 17 00:00:00 2001 From: Mayank Tyagi Date: Fri, 15 May 2026 17:51:53 +0530 Subject: [PATCH 02/13] fix(nimble): Forward HID report/control/protocol writes as HIDD events --- components/bt/host/nimble/nimble | 2 +- components/esp_hid/src/ble_hidh.c | 17 +++- components/esp_hid/src/nimble_hidd.c | 132 +++++++++++++++++++++++++++ 3 files changed, 149 insertions(+), 2 deletions(-) diff --git a/components/bt/host/nimble/nimble b/components/bt/host/nimble/nimble index 5045cb5eb88..61a88ab53bd 160000 --- a/components/bt/host/nimble/nimble +++ b/components/bt/host/nimble/nimble @@ -1 +1 @@ -Subproject commit 5045cb5eb885acb84d4ea86fbf9e5e0202cf0e84 +Subproject commit 61a88ab53bde2aef4b9022fbd43368eff50c5a1f diff --git a/components/esp_hid/src/ble_hidh.c b/components/esp_hid/src/ble_hidh.c index b10119ed9b3..90ae8cb14e8 100644 --- a/components/esp_hid/src/ble_hidh.c +++ b/components/esp_hid/src/ble_hidh.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2017-2024 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2017-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -302,6 +302,21 @@ static void attach_report_listeners(esp_gatt_if_t gattc_if, esp_hidh_dev_t *dev) //subscribe to battery notifications if (dev->ble.battery_handle) { + uint8_t *rdata = NULL; + uint16_t rlen = 0; + + if (event_loop_handle && + read_char(gattc_if, dev->ble.conn_id, dev->ble.battery_handle, + ESP_GATT_AUTH_REQ_NO_MITM, &rdata, &rlen) == ESP_GATT_OK && + rlen >= 1 && rdata != NULL) { + esp_hidh_event_data_t p = {0}; + p.battery.dev = dev; + p.battery.level = rdata[0]; + esp_event_post_to(event_loop_handle, ESP_HIDH_EVENTS, ESP_HIDH_BATTERY_EVENT, + &p, sizeof(esp_hidh_event_data_t), portMAX_DELAY); + } + free(rdata); + register_for_notify(gattc_if, dev->addr.bda, dev->ble.battery_handle); if (dev->ble.battery_ccc_handle) { //Write CCC descr to enable notifications diff --git a/components/esp_hid/src/nimble_hidd.c b/components/esp_hid/src/nimble_hidd.c index 3324079afbd..aa4efda4bff 100644 --- a/components/esp_hid/src/nimble_hidd.c +++ b/components/esp_hid/src/nimble_hidd.c @@ -43,6 +43,9 @@ static void (*s_prev_sync_cb)(void) = NULL; static struct ble_gap_event_listener nimble_gap_event_listener; static void nimble_host_synced(void); void nimble_host_reset(int reason); +static void nimble_report_write_cb(uint16_t attr_handle, uint8_t report_type, uint8_t report_id, + const uint8_t *data, uint16_t len); +static void nimble_char_write_cb(uint16_t attr_handle, uint16_t char_uuid16, uint8_t value); static inline void lock_hidd(void) { @@ -349,6 +352,8 @@ static int nimble_hidd_dev_deinit(void *devp) ble_hs_cfg.sync_cb = s_prev_sync_cb; } ble_hs_cfg.gatts_register_cb = NULL; + ble_svc_hid_register_report_write_cb(NULL); + ble_svc_hid_register_char_write_cb(NULL); unlock_hidd(); /* Known timing issue: STOP_EVENT is posted here but ble_hid_free_config (called @@ -425,6 +430,129 @@ static hidd_le_report_item_t* find_report_by_usage_and_type(uint8_t dev_index, u return NULL; } +static void nimble_report_write_cb(uint16_t attr_handle, uint8_t report_type, uint8_t report_id, + const uint8_t *data, uint16_t len) +{ + lock_hidd(); + if (s_dev == NULL || s_dev->event_loop_handle == NULL || data == NULL) { + unlock_hidd(); + return; + } + + hidd_le_report_item_t *match = NULL; + uint8_t map_index = 0; + for (uint8_t d = 0; d < s_dev->devices_len && match == NULL; d++) { + for (uint8_t r = 0; r < s_dev->devices[d].reports_len; r++) { + hidd_le_report_item_t *item = &s_dev->devices[d].reports[r]; + if (item->handle == attr_handle) { + match = item; + map_index = d; + break; + } + } + } + + if (match == NULL) { + unlock_hidd(); + return; + } + + if (report_type != ESP_HID_REPORT_TYPE_OUTPUT && + report_type != ESP_HID_REPORT_TYPE_FEATURE) { + ESP_LOGD(TAG, "Ignoring host write for unsupported report type=%u, id=%u, handle=%u", + report_type, report_id, attr_handle); + unlock_hidd(); + return; + } + + size_t event_data_size = sizeof(esp_hidd_event_data_t); + if (len > 0) { + event_data_size += len; + } + esp_hidd_event_data_t *p_cb_param = (esp_hidd_event_data_t *)calloc(1, event_data_size); + if (p_cb_param == NULL) { + ESP_LOGE(TAG, "%s malloc event data failed!", __func__); + unlock_hidd(); + return; + } + + if (len > 0) { + memcpy(((uint8_t *)p_cb_param) + sizeof(esp_hidd_event_data_t), data, len); + } + + if (report_type == ESP_HID_REPORT_TYPE_OUTPUT) { + p_cb_param->output.dev = s_dev->dev; + p_cb_param->output.usage = match->usage; + p_cb_param->output.report_id = report_id; + p_cb_param->output.length = len; + p_cb_param->output.data = (len > 0) ? (uint8_t *)data : NULL; /* fixed by esp_hidd_process_event_data_handler */ + p_cb_param->output.map_index = map_index; + esp_event_post_to(s_dev->event_loop_handle, ESP_HIDD_EVENTS, ESP_HIDD_OUTPUT_EVENT, + p_cb_param, event_data_size, portMAX_DELAY); + } else if (report_type == ESP_HID_REPORT_TYPE_FEATURE) { + p_cb_param->feature.dev = s_dev->dev; + p_cb_param->feature.usage = match->usage; + p_cb_param->feature.report_id = report_id; + p_cb_param->feature.length = len; + p_cb_param->feature.data = (len > 0) ? (uint8_t *)data : NULL; /* fixed by esp_hidd_process_event_data_handler */ + p_cb_param->feature.map_index = map_index; + esp_event_post_to(s_dev->event_loop_handle, ESP_HIDD_EVENTS, ESP_HIDD_FEATURE_EVENT, + p_cb_param, event_data_size, portMAX_DELAY); + } + free(p_cb_param); + unlock_hidd(); +} + +static void nimble_char_write_cb(uint16_t attr_handle, uint16_t char_uuid16, uint8_t value) +{ + lock_hidd(); + if (s_dev == NULL || s_dev->event_loop_handle == NULL) { + unlock_hidd(); + return; + } + + uint8_t map_index = 0; + bool found = false; + + for (uint8_t d = 0; d < s_dev->devices_len; d++) { + if (char_uuid16 == BLE_SVC_HID_CHR_UUID16_PROTOCOL_MODE && + s_dev->devices[d].hid_protocol_handle == attr_handle) { + found = true; + map_index = d; + break; + } + if (char_uuid16 == BLE_SVC_HID_CHR_UUID16_HID_CTRL_PT && + s_dev->devices[d].hid_control_handle == attr_handle) { + found = true; + map_index = d; + break; + } + } + + if (!found) { + unlock_hidd(); + return; + } + + esp_hidd_event_data_t cb_param = {0}; + if (char_uuid16 == BLE_SVC_HID_CHR_UUID16_PROTOCOL_MODE) { + s_dev->protocol = value; + cb_param.protocol_mode.dev = s_dev->dev; + cb_param.protocol_mode.protocol_mode = value; + cb_param.protocol_mode.map_index = map_index; + esp_event_post_to(s_dev->event_loop_handle, ESP_HIDD_EVENTS, ESP_HIDD_PROTOCOL_MODE_EVENT, + &cb_param, sizeof(esp_hidd_event_data_t), portMAX_DELAY); + } else if (char_uuid16 == BLE_SVC_HID_CHR_UUID16_HID_CTRL_PT) { + s_dev->control = value; + cb_param.control.dev = s_dev->dev; + cb_param.control.control = value; + cb_param.control.map_index = map_index; + esp_event_post_to(s_dev->event_loop_handle, ESP_HIDD_EVENTS, ESP_HIDD_CONTROL_EVENT, + &cb_param, sizeof(esp_hidd_event_data_t), portMAX_DELAY); + } + unlock_hidd(); +} + static int nimble_hidd_dev_input_set(void *devp, size_t index, size_t id, uint8_t *data, size_t length) { hidd_le_report_item_t *p_rpt; @@ -843,6 +971,8 @@ esp_err_t esp_ble_hidd_dev_init(esp_hidd_dev_t *dev_p, const esp_hid_device_conf ble_hs_cfg.reset_cb = nimble_host_reset; ble_hs_cfg.sync_cb = nimble_host_synced; ble_hs_cfg.gatts_register_cb = nimble_gatt_svr_register_cb; + ble_svc_hid_register_report_write_cb(nimble_report_write_cb); + ble_svc_hid_register_char_write_cb(nimble_char_write_cb); rc = nimble_hid_start_gatts(); if (rc != ESP_OK) { if (ble_hs_cfg.reset_cb == nimble_host_reset) { @@ -852,6 +982,8 @@ esp_err_t esp_ble_hidd_dev_init(esp_hidd_dev_t *dev_p, const esp_hid_device_conf ble_hs_cfg.sync_cb = s_prev_sync_cb; } ble_hs_cfg.gatts_register_cb = NULL; + ble_svc_hid_register_report_write_cb(NULL); + ble_svc_hid_register_char_write_cb(NULL); ble_hidd_dev_free(); return rc; } From 7c151a70827b75377d97debfb483974ca26e8690 Mon Sep 17 00:00:00 2001 From: Mayank Tyagi Date: Fri, 15 May 2026 17:52:49 +0530 Subject: [PATCH 03/13] fix(nimble): Always read initial BAS level --- components/esp_hid/src/nimble_hidh.c | 20 ++++++++++++++++--- .../esp_hid_device/main/esp_hid_device_main.c | 2 ++ 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/components/esp_hid/src/nimble_hidh.c b/components/esp_hid/src/nimble_hidh.c index 4c63de9dc3d..a062ebd0b2f 100644 --- a/components/esp_hid/src/nimble_hidh.c +++ b/components/esp_hid/src/nimble_hidh.c @@ -70,14 +70,14 @@ static inline void SEND_CB(void) static inline void LOCK_OPS(void) { if (s_ble_hidh_op_mutex) { - xSemaphoreTake(s_ble_hidh_op_mutex, portMAX_DELAY); + xSemaphoreTakeRecursive(s_ble_hidh_op_mutex, portMAX_DELAY); } } static inline void UNLOCK_OPS(void) { if (s_ble_hidh_op_mutex) { - xSemaphoreGive(s_ble_hidh_op_mutex); + xSemaphoreGiveRecursive(s_ble_hidh_op_mutex); } } @@ -789,6 +789,20 @@ static void attach_report_listeners(esp_hidh_dev_t *dev) report = dev->reports; if (dev->ble.battery_handle) { + uint8_t *rdata = NULL; + uint16_t rlen = 0; + + if (event_loop_handle && + read_char(dev->ble.conn_id, dev->ble.battery_handle, &rdata, &rlen) == 0 && + rlen >= 1 && rdata != NULL) { + esp_hidh_event_data_t p = {0}; + p.battery.dev = dev; + p.battery.level = rdata[0]; + esp_event_post_to(event_loop_handle, ESP_HIDH_EVENTS, ESP_HIDH_BATTERY_EVENT, + &p, sizeof(esp_hidh_event_data_t), portMAX_DELAY); + } + free(rdata); + register_for_notify(dev->ble.conn_id, dev->ble.battery_handle); if (dev->ble.battery_ccc_handle && dev->ble.conn_id >= 0 && dev->connected) { write_char_descr(dev, dev->ble.battery_ccc_handle, 2, (uint8_t *)&ccc_data); @@ -1174,7 +1188,7 @@ esp_err_t esp_ble_hidh_init(const esp_hidh_config_t *config) s_ble_hidh_cb_semaphore = xSemaphoreCreateBinary(); ESP_RETURN_ON_FALSE(s_ble_hidh_cb_semaphore, ESP_ERR_NO_MEM, TAG, "Allocation failed"); - s_ble_hidh_op_mutex = xSemaphoreCreateMutex(); + s_ble_hidh_op_mutex = xSemaphoreCreateRecursiveMutex(); if (s_ble_hidh_op_mutex == NULL) { vSemaphoreDelete(s_ble_hidh_cb_semaphore); s_ble_hidh_cb_semaphore = NULL; diff --git a/examples/bluetooth/esp_hid_device/main/esp_hid_device_main.c b/examples/bluetooth/esp_hid_device/main/esp_hid_device_main.c index d0081b843be..39e760f7e55 100644 --- a/examples/bluetooth/esp_hid_device/main/esp_hid_device_main.c +++ b/examples/bluetooth/esp_hid_device/main/esp_hid_device_main.c @@ -41,6 +41,7 @@ #include "esp_hid_gap.h" static const char *TAG = "HID_DEV_DEMO"; +#define HID_BATTERY_LEVEL 60 typedef struct { @@ -941,6 +942,7 @@ void app_main(void) ESP_LOGI(TAG, "setting ble device"); ESP_ERROR_CHECK( esp_hidd_dev_init(&ble_hid_config, ESP_HID_TRANSPORT_BLE, ble_hidd_event_callback, &s_ble_hid_param.hid_dev)); + ESP_ERROR_CHECK(esp_hidd_dev_battery_set(s_ble_hid_param.hid_dev, HID_BATTERY_LEVEL)); #endif #if CONFIG_BT_HID_DEVICE_ENABLED From 562a32ba461579b69d148d24eab547ab0c6468c8 Mon Sep 17 00:00:00 2001 From: Mayank Tyagi Date: Mon, 25 May 2026 12:59:20 +0530 Subject: [PATCH 04/13] fix(nimble): Avoid deadlock caused by local writes taking s_hidd_mutex --- components/bt/host/nimble/nimble | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/bt/host/nimble/nimble b/components/bt/host/nimble/nimble index 61a88ab53bd..0176a098dcc 160000 --- a/components/bt/host/nimble/nimble +++ b/components/bt/host/nimble/nimble @@ -1 +1 @@ -Subproject commit 61a88ab53bde2aef4b9022fbd43368eff50c5a1f +Subproject commit 0176a098dccb0a99b44e7b272b73cfaf0e890da0 From 3b270b8c67ab4fe1d1b436c617c91684f84ea7c5 Mon Sep 17 00:00:00 2001 From: Rahul Tank Date: Mon, 25 May 2026 15:56:37 +0530 Subject: [PATCH 05/13] fix(nimble): Remove extra/unwanted prints --- components/bt/host/nimble/nimble | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/bt/host/nimble/nimble b/components/bt/host/nimble/nimble index 0176a098dcc..1fd2e39acfd 160000 --- a/components/bt/host/nimble/nimble +++ b/components/bt/host/nimble/nimble @@ -1 +1 @@ -Subproject commit 0176a098dccb0a99b44e7b272b73cfaf0e890da0 +Subproject commit 1fd2e39acfd2a7de27f337ee4b6c9e91bb320e07 From b2ab997d3f2ec0a282d040b52be3622e277c13ad Mon Sep 17 00:00:00 2001 From: Rahul Tank Date: Tue, 26 May 2026 22:00:39 +0530 Subject: [PATCH 06/13] fix(nimble): Fix compilation issues in Gatt SPS service --- components/bt/host/nimble/Kconfig.in | 8 ++++++++ components/bt/host/nimble/nimble | 2 +- .../nimble/ble_chan_sound_initiator/main/gatt_svr.c | 3 ++- .../nimble/ble_chan_sound_reflector/main/gatt_svr.c | 3 ++- .../bluetooth/nimble/ble_htp/htp_prph/main/gatt_svr.c | 2 +- 5 files changed, 14 insertions(+), 4 deletions(-) diff --git a/components/bt/host/nimble/Kconfig.in b/components/bt/host/nimble/Kconfig.in index 539482b93fc..6749440bc06 100644 --- a/components/bt/host/nimble/Kconfig.in +++ b/components/bt/host/nimble/Kconfig.in @@ -917,6 +917,14 @@ menu "Services" help Defines maximum number of report characteristics per service instance + config BT_NIMBLE_RAS_SERVICE + depends on BT_NIMBLE_CHANNEL_SOUNDING + bool "RAS service" + default y + help + Enable RAS Service + + menuconfig BT_NIMBLE_BAS_SERVICE bool "Battery service" default y diff --git a/components/bt/host/nimble/nimble b/components/bt/host/nimble/nimble index 1fd2e39acfd..0139d977c1a 160000 --- a/components/bt/host/nimble/nimble +++ b/components/bt/host/nimble/nimble @@ -1 +1 @@ -Subproject commit 1fd2e39acfd2a7de27f337ee4b6c9e91bb320e07 +Subproject commit 0139d977c1aeed781dddf931606ee7b049eeadf6 diff --git a/examples/bluetooth/nimble/ble_chan_sound_initiator/main/gatt_svr.c b/examples/bluetooth/nimble/ble_chan_sound_initiator/main/gatt_svr.c index 168d0035c79..2f06346da8c 100644 --- a/examples/bluetooth/nimble/ble_chan_sound_initiator/main/gatt_svr.c +++ b/examples/bluetooth/nimble/ble_chan_sound_initiator/main/gatt_svr.c @@ -31,7 +31,8 @@ gatt_svr_init(void) #if MYNEWT_VAL(BLE_GATTS) ble_svc_gatt_init(); #endif +#if MYNEWT_VAL(BLE_GATTS) && CONFIG_BT_NIMBLE_RAS_SERVICE ble_svc_ras_init(); - +#endif return 0; } diff --git a/examples/bluetooth/nimble/ble_chan_sound_reflector/main/gatt_svr.c b/examples/bluetooth/nimble/ble_chan_sound_reflector/main/gatt_svr.c index fdadd4c2aa7..f559b1d6f34 100644 --- a/examples/bluetooth/nimble/ble_chan_sound_reflector/main/gatt_svr.c +++ b/examples/bluetooth/nimble/ble_chan_sound_reflector/main/gatt_svr.c @@ -31,7 +31,8 @@ custom_gatt_svr_init(void) #if MYNEWT_VAL(BLE_GATTS) ble_svc_gatt_init(); #endif +#if MYNEWT_VAL(BLE_GATTS) && CONFIG_BT_NIMBLE_RAS_SERVICE ble_svc_ras_init(); - +#endif return 0; } diff --git a/examples/bluetooth/nimble/ble_htp/htp_prph/main/gatt_svr.c b/examples/bluetooth/nimble/ble_htp/htp_prph/main/gatt_svr.c index 82b6b1f1bcf..aae6bd14d51 100644 --- a/examples/bluetooth/nimble/ble_htp/htp_prph/main/gatt_svr.c +++ b/examples/bluetooth/nimble/ble_htp/htp_prph/main/gatt_svr.c @@ -123,7 +123,7 @@ gatt_svr_init(void) #if MYNEWT_VAL(BLE_GATTS) ble_svc_gatt_init(); #endif -#if CONFIG_BT_NIMBLE_ANS_SERVICE +#if CONFIG_BT_NIMBLE_HTP_SERVICE ble_svc_htp_init(); #endif From b633c5e2a54be4879ebbe7e972872f7cdf643270 Mon Sep 17 00:00:00 2001 From: Rahul Tank Date: Wed, 29 Apr 2026 17:47:27 +0530 Subject: [PATCH 07/13] fix(nimble): Add support for shorter connection intervals --- components/bt/common/Kconfig.in | 15 +++++++++++++++ components/bt/controller/esp32c3/Kconfig.in | 11 ++++------- components/bt/host/bluedroid/Kconfig.in | 17 ----------------- components/bt/host/nimble/nimble | 2 +- 4 files changed, 20 insertions(+), 25 deletions(-) diff --git a/components/bt/common/Kconfig.in b/components/bt/common/Kconfig.in index 8f50fd6f941..d4c16df32bc 100644 --- a/components/bt/common/Kconfig.in +++ b/components/bt/common/Kconfig.in @@ -6,6 +6,21 @@ config BT_ALARM_MAX_NUM This option decides the maximum number of alarms which could be used by Bluetooth host. +config BT_BLE_HOST_ALLOW_SUB_SPEC_MIN_CONN_INT + bool "Allow BLE connection interval below Bluetooth Core Spec minimum (disable host check)" + depends on BT_BLE_ENABLED || BT_NIMBLE_ENABLED + default n + help + When enabled, BLE host-side validation accepts connection interval + values below the Bluetooth Core Specification minimum of 0x0006 + (7.5 ms), down to non-zero values. The BLE controller still enforces + what is actually supported in hardware and firmware. + + End users should NOT set this option directly. In typical IDF builds it + follows the active Controller integration when that Controller supports + this mode; use the Controller's own configuration instead of toggling + this host symbol manually. + choice BT_SMP_CRYPTO_STACK prompt "SMP cryptographic stack" depends on (BT_BLE_SMP_ENABLE || BT_SMP_ENABLE || BT_NIMBLE_SECURITY_ENABLE || BT_LE_SECURITY_ENABLE) diff --git a/components/bt/controller/esp32c3/Kconfig.in b/components/bt/controller/esp32c3/Kconfig.in index 25d4a0ffd5c..383ebb16b4e 100644 --- a/components/bt/controller/esp32c3/Kconfig.in +++ b/components/bt/controller/esp32c3/Kconfig.in @@ -575,7 +575,7 @@ config BT_CTRL_CHECK_CONNECT_IND_ACCESS_ADDRESS config BT_CTRL_BLE_MIN_CONN_INTERVAL_ENABLE bool "Allow BLE connection interval below the spec minimum" default y - select BT_BLE_HOST_ALLOW_SUB_SPEC_MIN_CONN_INT if BT_BLUEDROID_ENABLED + select BT_BLE_HOST_ALLOW_SUB_SPEC_MIN_CONN_INT if BT_BLUEDROID_ENABLED || BT_NIMBLE_ENABLED help Enabling this option allows the BLE controller to use a connection interval smaller than the Bluetooth Core specification minimum of 7.5 ms. On @@ -590,12 +590,9 @@ config BT_CTRL_BLE_MIN_CONN_INTERVAL_ENABLE This option is enabled by default. Disable it to stay compliant with the BLE specification (minimum connection interval 7.5 ms). - Host stack: When Bluedroid is the BLE host, enabling this option also selects - BT_BLE_HOST_ALLOW_SUB_SPEC_MIN_CONN_INT so the host accepts connection - intervals below the spec minimum. NimBLE host does not provide equivalent - support yet; it is planned for a future release. Until then, use Bluedroid - if you need coordinated host and controller behavior for sub-minimum - intervals. + Host stack: Enabling this option also selects + BT_BLE_HOST_ALLOW_SUB_SPEC_MIN_CONN_INT so the active BLE host accepts + connection intervals below the spec minimum. menu "Controller debug log Options (Experimental)" config BT_CTRL_LE_LOG_EN diff --git a/components/bt/host/bluedroid/Kconfig.in b/components/bt/host/bluedroid/Kconfig.in index c357d1d6931..a230975b2f3 100644 --- a/components/bt/host/bluedroid/Kconfig.in +++ b/components/bt/host/bluedroid/Kconfig.in @@ -1388,23 +1388,6 @@ config BT_BLE_RPA_TIMEOUT This set RPA timeout of Controller and Host. Default is 900 s (15 minutes). Range is 1 s to 1 hour (3600 s). -config BT_BLE_HOST_ALLOW_SUB_SPEC_MIN_CONN_INT - bool "Allow BLE connection interval below Bluetooth Core Spec minimum (disable host check)" - depends on BT_BLE_ENABLED - default n - help - When enabled, the Bluedroid host skips the minimum BLE connection - interval validation (Bluetooth Core Spec minimum is 0x0006 / 7.5 ms) - and accepts any non-zero interval value from the application. The - BLE controller then enforces what is actually allowed; how small the - connection interval may be depends on controller capability and its - own configuration, not on this host option text. - - End users should NOT set this option directly. In typical IDF builds it - follows the active Controller integration when that Controller supports - this mode; use the Controller's own configuration (menu entries and symbol - names differ by chip) instead of toggling this host symbol manually. - menuconfig BT_BLE_50_FEATURES_SUPPORTED bool "Enable BLE 5.0 and above features(please disable BLE 4.2 if enable BLE 5.0)" depends on (BT_BLE_ENABLED && ((BT_CONTROLLER_ENABLED && SOC_BLE_50_SUPPORTED) || BT_CONTROLLER_DISABLED)) diff --git a/components/bt/host/nimble/nimble b/components/bt/host/nimble/nimble index 0139d977c1a..0364c41e599 160000 --- a/components/bt/host/nimble/nimble +++ b/components/bt/host/nimble/nimble @@ -1 +1 @@ -Subproject commit 0139d977c1aeed781dddf931606ee7b049eeadf6 +Subproject commit 0364c41e59988c3a94020cbc539b632c54fa8488 From 609ad133245393ad18b794462b4b5f6c0ee2ebc5 Mon Sep 17 00:00:00 2001 From: Rahul Tank Date: Thu, 4 Jun 2026 17:19:21 +0530 Subject: [PATCH 08/13] fix(nimble): Fix for clang compilation issues --- components/bt/host/nimble/nimble | 2 +- .../ble_periodic_adv_with_cte/main/main.c | 5 +++ .../sdkconfig.defaults | 10 ++++-- .../sdkconfig.defaults.esp32h2 | 7 +++-- .../nimble/ble_cts/cts_cent/main/main.c | 30 ++---------------- .../enc_adv_data_cent/main/main.c | 22 ++----------- .../nimble/ble_htp/htp_cent/main/main.c | 31 ++++--------------- .../ble_l2cap_coc/coc_blecent/main/main.c | 30 ++---------------- .../ble_l2cap_coc/coc_bleprph/main/main.c | 4 +-- .../ble_multi_conn_cent/main/main.c | 6 ++++ .../ble_multi_conn_cent/sdkconfig.defaults | 9 +++--- .../ble_multi_conn_prph/sdkconfig.defaults | 9 +++--- .../nimble/ble_phy/phy_cent/main/main.c | 29 +++-------------- .../proximity_sensor_cent/main/main.c | 29 +++-------------- examples/bluetooth/nimble/blecent/main/main.c | 29 ++--------------- .../common/nimble_central_utils/esp_central.h | 1 + .../nimble/common/nimble_central_utils/misc.c | 14 +++++++++ .../components/cmd_system/cmd_system.c | 3 +- .../blecent_throughput/main/gattc.h | 3 ++ .../blecent_throughput/main/main.c | 26 +++------------- .../blecent_throughput/main/misc.c | 13 +++++++- 21 files changed, 99 insertions(+), 213 deletions(-) diff --git a/components/bt/host/nimble/nimble b/components/bt/host/nimble/nimble index 0364c41e599..f97c9c55f61 160000 --- a/components/bt/host/nimble/nimble +++ b/components/bt/host/nimble/nimble @@ -1 +1 @@ -Subproject commit 0364c41e59988c3a94020cbc539b632c54fa8488 +Subproject commit f97c9c55f6146739417b96dc5b94881bfe1ce902 diff --git a/examples/bluetooth/nimble/ble_cte/ble_periodic_adv_with_cte/main/main.c b/examples/bluetooth/nimble/ble_cte/ble_periodic_adv_with_cte/main/main.c index 21c701170fb..ac8941cec61 100644 --- a/examples/bluetooth/nimble/ble_cte/ble_periodic_adv_with_cte/main/main.c +++ b/examples/bluetooth/nimble/ble_cte/ble_periodic_adv_with_cte/main/main.c @@ -19,6 +19,11 @@ static const char *TAG = "CTE_ADV_EXAMPLE"; static uint8_t s_periodic_adv_raw_data[] = {0x0D, BLE_HS_ADV_TYPE_COMP_NAME, 'C','T','E',' ','P','e','r','i','o','d','i','c'}; +#if !(MYNEWT_VAL(BLE_EXT_ADV) && MYNEWT_VAL(BLE_PERIODIC_ADV) && MYNEWT_VAL(BLE_AOA_AOD)) +#error "This example requires NimBLE Extended Advertising, Periodic Advertising, and CTE (AoA/AoD). " \ + "Use a supported target from README.md (e.g. esp32h2, esp32c5, esp32c61) and run idf.py set-target before build." +#endif + /** * @brief Configure and start periodic advertising with CTE */ diff --git a/examples/bluetooth/nimble/ble_cte/ble_periodic_adv_with_cte/sdkconfig.defaults b/examples/bluetooth/nimble/ble_cte/ble_periodic_adv_with_cte/sdkconfig.defaults index 4e27f1576f2..2a58e694c98 100644 --- a/examples/bluetooth/nimble/ble_cte/ble_periodic_adv_with_cte/sdkconfig.defaults +++ b/examples/bluetooth/nimble/ble_cte/ble_periodic_adv_with_cte/sdkconfig.defaults @@ -1,7 +1,11 @@ -# This file was generated using idf.py save-defconfig. It can be edited manually. -# Espressif IoT Development Framework (ESP-IDF) 5.5.0 Project Minimal Configuration +# Minimal NimBLE config for periodic advertising with CTE (requires BLE 5.0). # +# Supported targets: ESP32-H2, ESP32-C5, ESP32-C61, etc. (see README.md). +# Run: idf.py set-target before building. + CONFIG_BT_ENABLED=y CONFIG_BT_NIMBLE_ENABLED=y -CONFIG_BT_NIMBLE_AOA_AOD=y +CONFIG_BT_NIMBLE_50_FEATURE_SUPPORT=y CONFIG_BT_NIMBLE_EXT_ADV=y +CONFIG_BT_NIMBLE_ENABLE_PERIODIC_ADV=y +CONFIG_BT_NIMBLE_AOA_AOD=y diff --git a/examples/bluetooth/nimble/ble_cte/ble_periodic_adv_with_cte/sdkconfig.defaults.esp32h2 b/examples/bluetooth/nimble/ble_cte/ble_periodic_adv_with_cte/sdkconfig.defaults.esp32h2 index 9543898142c..c0a363679de 100644 --- a/examples/bluetooth/nimble/ble_cte/ble_periodic_adv_with_cte/sdkconfig.defaults.esp32h2 +++ b/examples/bluetooth/nimble/ble_cte/ble_periodic_adv_with_cte/sdkconfig.defaults.esp32h2 @@ -1,5 +1,6 @@ -# This file was generated using idf.py save-defconfig. It can be edited manually. -# Espressif IoT Development Framework (ESP-IDF) 5.5.0 Project Minimal Configuration -# CONFIG_IDF_TARGET="esp32h2" CONFIG_BT_NIMBLE_SECURITY_ENABLE=n +CONFIG_BT_NIMBLE_50_FEATURE_SUPPORT=y +CONFIG_BT_NIMBLE_EXT_ADV=y +CONFIG_BT_NIMBLE_ENABLE_PERIODIC_ADV=y +CONFIG_BT_NIMBLE_AOA_AOD=y diff --git a/examples/bluetooth/nimble/ble_cts/cts_cent/main/main.c b/examples/bluetooth/nimble/ble_cts/cts_cent/main/main.c index bd765009ea2..5a358a0c363 100644 --- a/examples/bluetooth/nimble/ble_cts/cts_cent/main/main.c +++ b/examples/bluetooth/nimble/ble_cts/cts_cent/main/main.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2017-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2017-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -207,10 +207,6 @@ ext_ble_cts_cent_should_connect(const struct ble_gap_ext_disc_desc *disc) int offset = 0; int ad_struct_len = 0; uint8_t test_addr[6]; - uint32_t peer_addr[6]; - - memset(peer_addr, 0x0, sizeof peer_addr); - if (disc->legacy_event_type != BLE_HCI_ADV_RPT_EVTYPE_ADV_IND && disc->legacy_event_type != BLE_HCI_ADV_RPT_EVTYPE_DIR_IND) { return 0; @@ -219,15 +215,7 @@ ext_ble_cts_cent_should_connect(const struct ble_gap_ext_disc_desc *disc) ESP_LOGI(tag, "Peer address from menuconfig: %s", CONFIG_EXAMPLE_PEER_ADDR); /* Convert string to address */ - sscanf(CONFIG_EXAMPLE_PEER_ADDR, "%lx:%lx:%lx:%lx:%lx:%lx", - &peer_addr[5], &peer_addr[4], &peer_addr[3], - &peer_addr[2], &peer_addr[1], &peer_addr[0]); - - /* Conversion */ - for(int i=0; i<6; i++) { - test_addr[i] = (uint8_t )peer_addr[i]; - } - + peer_addr_parse(CONFIG_EXAMPLE_PEER_ADDR, test_addr); if (memcmp(test_addr, disc->addr.val, sizeof(disc->addr.val)) != 0) { return 0; } @@ -268,10 +256,6 @@ ble_cts_cent_should_connect(const struct ble_gap_disc_desc *disc) int rc; int i; uint8_t test_addr[6]; - uint32_t peer_addr[6]; - - memset(peer_addr, 0x0, sizeof peer_addr); - /* The device has to be advertising connectability. */ if (disc->event_type != BLE_HCI_ADV_RPT_EVTYPE_ADV_IND && disc->event_type != BLE_HCI_ADV_RPT_EVTYPE_DIR_IND) { @@ -287,15 +271,7 @@ ble_cts_cent_should_connect(const struct ble_gap_disc_desc *disc) if (strlen(CONFIG_EXAMPLE_PEER_ADDR) && (strncmp(CONFIG_EXAMPLE_PEER_ADDR, "ADDR_ANY", strlen("ADDR_ANY")) != 0)) { ESP_LOGI(tag, "Peer address from menuconfig: %s", CONFIG_EXAMPLE_PEER_ADDR); /* Convert string to address */ - sscanf(CONFIG_EXAMPLE_PEER_ADDR, "%lx:%lx:%lx:%lx:%lx:%lx", - &peer_addr[5], &peer_addr[4], &peer_addr[3], - &peer_addr[2], &peer_addr[1], &peer_addr[0]); - - /* Conversion */ - for (int i=0; i<6; i++) { - test_addr[i] = (uint8_t )peer_addr[i]; - } - + peer_addr_parse(CONFIG_EXAMPLE_PEER_ADDR, test_addr); if (memcmp(test_addr, disc->addr.val, sizeof(disc->addr.val)) != 0) { return 0; } diff --git a/examples/bluetooth/nimble/ble_enc_adv_data/enc_adv_data_cent/main/main.c b/examples/bluetooth/nimble/ble_enc_adv_data/enc_adv_data_cent/main/main.c index 35dda500166..3d17bd4e76d 100644 --- a/examples/bluetooth/nimble/ble_enc_adv_data/enc_adv_data_cent/main/main.c +++ b/examples/bluetooth/nimble/ble_enc_adv_data/enc_adv_data_cent/main/main.c @@ -322,10 +322,6 @@ enc_adv_data_cent_ext_should_connect(const struct ble_gap_ext_disc_desc *disc) uint32_t *addr_offset; #endif // CONFIG_EXAMPLE_USE_CI_ADDRESS uint8_t test_addr[6]; - uint32_t peer_addr[6]; - - memset(peer_addr, 0x0, sizeof peer_addr); - if (disc->legacy_event_type != BLE_HCI_ADV_RPT_EVTYPE_ADV_IND && disc->legacy_event_type != BLE_HCI_ADV_RPT_EVTYPE_DIR_IND) { return 0; @@ -334,9 +330,7 @@ enc_adv_data_cent_ext_should_connect(const struct ble_gap_ext_disc_desc *disc) #if !CONFIG_EXAMPLE_USE_CI_ADDRESS ESP_LOGI(tag, "Peer address from menuconfig: %s", CONFIG_EXAMPLE_PEER_ADDR); /* Convert string to address */ - sscanf(CONFIG_EXAMPLE_PEER_ADDR, "%lx:%lx:%lx:%lx:%lx:%lx", - &peer_addr[5], &peer_addr[4], &peer_addr[3], - &peer_addr[2], &peer_addr[1], &peer_addr[0]); + peer_addr_parse(CONFIG_EXAMPLE_PEER_ADDR, test_addr); #endif /* Conversion */ @@ -406,10 +400,6 @@ enc_adv_data_cent_should_connect(const struct ble_gap_disc_desc *disc) int rc; int i; uint8_t test_addr[6]; - uint32_t peer_addr[6]; - - memset(peer_addr, 0x0, sizeof peer_addr); - if (disc->event_type != BLE_HCI_ADV_RPT_EVTYPE_ADV_IND && disc->event_type != BLE_HCI_ADV_RPT_EVTYPE_DIR_IND) { return 0; @@ -423,15 +413,7 @@ enc_adv_data_cent_should_connect(const struct ble_gap_disc_desc *disc) if (strlen(CONFIG_EXAMPLE_PEER_ADDR) && (strncmp(CONFIG_EXAMPLE_PEER_ADDR, "ADDR_ANY", strlen ("ADDR_ANY")) != 0)) { MODLOG_DFLT(INFO, "Peer address from menuconfig: %s", CONFIG_EXAMPLE_PEER_ADDR); /* Convert string to address */ - sscanf(CONFIG_EXAMPLE_PEER_ADDR, "%lx:%lx:%lx:%lx:%lx:%lx", - &peer_addr[5], &peer_addr[4], &peer_addr[3], - &peer_addr[2], &peer_addr[1], &peer_addr[0]); - - /* Conversion */ - for (int i=0; i<6; i++) { - test_addr[i] = (uint8_t )peer_addr[i]; - } - + peer_addr_parse(CONFIG_EXAMPLE_PEER_ADDR, test_addr); if (memcmp(test_addr, disc->addr.val, sizeof(disc->addr.val)) != 0) { return 0; } diff --git a/examples/bluetooth/nimble/ble_htp/htp_cent/main/main.c b/examples/bluetooth/nimble/ble_htp/htp_cent/main/main.c index 81bf979739a..ccc03dbecac 100644 --- a/examples/bluetooth/nimble/ble_htp/htp_cent/main/main.c +++ b/examples/bluetooth/nimble/ble_htp/htp_cent/main/main.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2017-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2017-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -320,10 +320,7 @@ ext_ble_htp_cent_should_connect(const struct ble_gap_ext_disc_desc *disc) int offset = 0; int ad_struct_len = 0; uint8_t test_addr[6]; - uint32_t peer_addr[6]; - - memset(peer_addr, 0x0, sizeof peer_addr); - + uint8_t parsed_addr[6]; if (disc->legacy_event_type != BLE_HCI_ADV_RPT_EVTYPE_ADV_IND && disc->legacy_event_type != BLE_HCI_ADV_RPT_EVTYPE_DIR_IND) { return 0; @@ -331,13 +328,9 @@ ext_ble_htp_cent_should_connect(const struct ble_gap_ext_disc_desc *disc) if (strlen(CONFIG_EXAMPLE_PEER_ADDR) && (strncmp(CONFIG_EXAMPLE_PEER_ADDR, "ADDR_ANY", strlen ("ADDR_ANY")) != 0)) { ESP_LOGI(tag, "Peer address from menuconfig: %s", CONFIG_EXAMPLE_PEER_ADDR); /* Convert string to address */ - sscanf(CONFIG_EXAMPLE_PEER_ADDR, "%lx:%lx:%lx:%lx:%lx:%lx", - &peer_addr[5], &peer_addr[4], &peer_addr[3], - &peer_addr[2], &peer_addr[1], &peer_addr[0]); - - /* Conversion */ - for (int i=0; i<6; i++) { - test_addr[i] = (uint8_t )peer_addr[5 - i]; + peer_addr_parse(CONFIG_EXAMPLE_PEER_ADDR, parsed_addr); + for (int i = 0; i < 6; i++) { + test_addr[i] = parsed_addr[5 - i]; } if (memcmp(test_addr, disc->addr.val, sizeof(disc->addr.val)) != 0) { @@ -377,10 +370,6 @@ ble_htp_cent_should_connect(const struct ble_gap_disc_desc *disc) int rc; int i; uint8_t test_addr[6]; - uint32_t peer_addr[6]; - - memset(peer_addr, 0x0, sizeof peer_addr); - /* The device has to be advertising connectability. */ if (disc->event_type != BLE_HCI_ADV_RPT_EVTYPE_ADV_IND && disc->event_type != BLE_HCI_ADV_RPT_EVTYPE_DIR_IND) { @@ -396,15 +385,7 @@ ble_htp_cent_should_connect(const struct ble_gap_disc_desc *disc) if (strlen(CONFIG_EXAMPLE_PEER_ADDR) && (strncmp(CONFIG_EXAMPLE_PEER_ADDR, "ADDR_ANY", strlen("ADDR_ANY")) != 0)) { ESP_LOGI(tag, "Peer address from menuconfig: %s", CONFIG_EXAMPLE_PEER_ADDR); /* Convert string to address */ - sscanf(CONFIG_EXAMPLE_PEER_ADDR, "%lx:%lx:%lx:%lx:%lx:%lx", - &peer_addr[5], &peer_addr[4], &peer_addr[3], - &peer_addr[2], &peer_addr[1], &peer_addr[0]); - - /* Conversion */ - for (int i=0; i<6; i++) { - test_addr[i] = (uint8_t )peer_addr[i]; - } - + peer_addr_parse(CONFIG_EXAMPLE_PEER_ADDR, test_addr); if (memcmp(test_addr, disc->addr.val, sizeof(disc->addr.val)) != 0) { return 0; } diff --git a/examples/bluetooth/nimble/ble_l2cap_coc/coc_blecent/main/main.c b/examples/bluetooth/nimble/ble_l2cap_coc/coc_blecent/main/main.c index e6cff455f2f..c7f4fa68efe 100644 --- a/examples/bluetooth/nimble/ble_l2cap_coc/coc_blecent/main/main.c +++ b/examples/bluetooth/nimble/ble_l2cap_coc/coc_blecent/main/main.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2021-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2021-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Unlicense OR CC0-1.0 */ @@ -266,10 +266,6 @@ ext_blecent_should_connect(const struct ble_gap_ext_disc_desc *disc) int offset = 0; int ad_struct_len = 0; uint8_t test_addr[6]; - uint32_t peer_addr[6]; - - memset(peer_addr, 0x0, sizeof peer_addr); - if (disc->legacy_event_type != BLE_HCI_ADV_RPT_EVTYPE_ADV_IND && disc->legacy_event_type != BLE_HCI_ADV_RPT_EVTYPE_DIR_IND) { return 0; @@ -278,15 +274,7 @@ ext_blecent_should_connect(const struct ble_gap_ext_disc_desc *disc) (strncmp(CONFIG_EXAMPLE_PEER_ADDR, "ADDR_ANY", strlen("ADDR_ANY")) != 0)) { ESP_LOGI(tag, "Peer address from menuconfig: %s", CONFIG_EXAMPLE_PEER_ADDR); /* Convert string to address */ - sscanf(CONFIG_EXAMPLE_PEER_ADDR, "%lx:%lx:%lx:%lx:%lx:%lx", - &peer_addr[5], &peer_addr[4], &peer_addr[3], - &peer_addr[2], &peer_addr[1], &peer_addr[0]); - - /* Conversion */ - for (int i=0; i<6; i++) { - test_addr[i] = (uint8_t )peer_addr[i]; - } - + peer_addr_parse(CONFIG_EXAMPLE_PEER_ADDR, test_addr); if (memcmp(test_addr, disc->addr.val, sizeof(disc->addr.val)) != 0) { return 0; } @@ -329,10 +317,6 @@ blecent_should_connect(const struct ble_gap_disc_desc *disc) int rc; int i; uint8_t test_addr[6]; - uint32_t peer_addr[6]; - - memset(peer_addr, 0x0, sizeof peer_addr); - /* The device has to be advertising connectability. */ if (disc->event_type != BLE_HCI_ADV_RPT_EVTYPE_ADV_IND && disc->event_type != BLE_HCI_ADV_RPT_EVTYPE_DIR_IND) { @@ -349,15 +333,7 @@ blecent_should_connect(const struct ble_gap_disc_desc *disc) (strncmp(CONFIG_EXAMPLE_PEER_ADDR, "ADDR_ANY", strlen("ADDR_ANY")) != 0)) { MODLOG_DFLT(INFO, "Peer address from menuconfig:%s", CONFIG_EXAMPLE_PEER_ADDR); /* Convert string to address */ - sscanf(CONFIG_EXAMPLE_PEER_ADDR, "%lx:%lx:%lx:%lx:%lx:%lx", - &peer_addr[5], &peer_addr[4], &peer_addr[3], - &peer_addr[2], &peer_addr[1], &peer_addr[0]); - - /* Conversion */ - for (int i=0; i<6; i++) { - test_addr[i] = (uint8_t )peer_addr[i]; - } - + peer_addr_parse(CONFIG_EXAMPLE_PEER_ADDR, test_addr); if (memcmp(test_addr, disc->addr.val, sizeof(disc->addr.val)) != 0) { return 0; } diff --git a/examples/bluetooth/nimble/ble_l2cap_coc/coc_bleprph/main/main.c b/examples/bluetooth/nimble/ble_l2cap_coc/coc_bleprph/main/main.c index a8e5f3b8df0..3f3c46a1663 100644 --- a/examples/bluetooth/nimble/ble_l2cap_coc/coc_bleprph/main/main.c +++ b/examples/bluetooth/nimble/ble_l2cap_coc/coc_bleprph/main/main.c @@ -196,8 +196,8 @@ bleprph_l2cap_coc_accept(uint16_t conn_handle, uint16_t peer_mtu, { struct os_mbuf *sdu_rx; - console_printf("LE CoC accepting, chan: 0x%08lx, peer_mtu %d\n", - (uint32_t) chan, peer_mtu); + console_printf("LE CoC accepting, chan: 0x%08x, peer_mtu %d\n", + (unsigned) (uint32_t) chan, peer_mtu); sdu_rx = os_mbuf_get_pkthdr(&sdu_os_mbuf_pool, 0); if (!sdu_rx) { diff --git a/examples/bluetooth/nimble/ble_multi_conn/ble_multi_conn_cent/main/main.c b/examples/bluetooth/nimble/ble_multi_conn/ble_multi_conn_cent/main/main.c index a03a6bac9b3..d79daed6f84 100644 --- a/examples/bluetooth/nimble/ble_multi_conn/ble_multi_conn_cent/main/main.c +++ b/examples/bluetooth/nimble/ble_multi_conn/ble_multi_conn_cent/main/main.c @@ -14,6 +14,12 @@ #include "services/gap/ble_svc_gap.h" #include "ble_multi_conn_cent.h" +#if !MYNEWT_VAL(BLE_EXT_ADV) || !MYNEWT_VAL(OPTIMIZE_MULTI_CONN) +#error "This example requires NimBLE Extended Advertising and multi-connection optimization. " \ + "Enable BT_NIMBLE_50_FEATURE_SUPPORT, BT_NIMBLE_EXT_ADV, and BT_NIMBLE_OPTIMIZE_MULTI_CONN; " \ + "use a supported target from README.md (e.g. esp32h2, esp32c6) and run idf.py set-target." +#endif + #define BLE_PEER_NAME "esp-multi-conn" #define BLE_PEER_MAX_NUM (MYNEWT_VAL(BLE_MAX_CONNECTIONS) - 1) #define BLE_PREF_EVT_LEN_MS (5) diff --git a/examples/bluetooth/nimble/ble_multi_conn/ble_multi_conn_cent/sdkconfig.defaults b/examples/bluetooth/nimble/ble_multi_conn/ble_multi_conn_cent/sdkconfig.defaults index 762eb4d35ee..5ed952b38ab 100644 --- a/examples/bluetooth/nimble/ble_multi_conn/ble_multi_conn_cent/sdkconfig.defaults +++ b/examples/bluetooth/nimble/ble_multi_conn/ble_multi_conn_cent/sdkconfig.defaults @@ -1,10 +1,11 @@ -# This file was generated using idf.py save-defconfig. It can be edited manually. -# Espressif IoT Development Framework (ESP-IDF) Project Minimal Configuration -# +# Minimal NimBLE config for multi-connection central (requires BLE 5.0 ext adv). +# Supported targets: ESP32-C5, ESP32-C6, ESP32-C61, ESP32-H2, etc. (see README.md). + CONFIG_BT_ENABLED=y CONFIG_BT_NIMBLE_ENABLED=y -CONFIG_BT_NIMBLE_HCI_EVT_BUF_SIZE=70 +CONFIG_BT_NIMBLE_50_FEATURE_SUPPORT=y CONFIG_BT_NIMBLE_EXT_ADV=y +CONFIG_BT_NIMBLE_TRANSPORT_EVT_SIZE=70 CONFIG_BT_NIMBLE_MAX_CONNECTIONS=70 CONFIG_BT_NIMBLE_GATT_MAX_PROCS=70 CONFIG_BT_NIMBLE_MSYS_1_BLOCK_COUNT=100 diff --git a/examples/bluetooth/nimble/ble_multi_conn/ble_multi_conn_prph/sdkconfig.defaults b/examples/bluetooth/nimble/ble_multi_conn/ble_multi_conn_prph/sdkconfig.defaults index 6040051b36c..2d10b8c6709 100644 --- a/examples/bluetooth/nimble/ble_multi_conn/ble_multi_conn_prph/sdkconfig.defaults +++ b/examples/bluetooth/nimble/ble_multi_conn/ble_multi_conn_prph/sdkconfig.defaults @@ -1,10 +1,11 @@ -# This file was generated using idf.py save-defconfig. It can be edited manually. -# Espressif IoT Development Framework (ESP-IDF) Project Minimal Configuration -# +# Minimal NimBLE config for multi-connection peripheral (requires BLE 5.0 ext adv). +# Supported targets: ESP32-C5, ESP32-C6, ESP32-C61, ESP32-H2, etc. (see README.md). + CONFIG_BT_ENABLED=y CONFIG_BT_NIMBLE_ENABLED=y -CONFIG_BT_NIMBLE_HCI_EVT_BUF_SIZE=70 +CONFIG_BT_NIMBLE_50_FEATURE_SUPPORT=y CONFIG_BT_NIMBLE_EXT_ADV=y +CONFIG_BT_NIMBLE_TRANSPORT_EVT_SIZE=70 CONFIG_BT_NIMBLE_MAX_CONNECTIONS=69 CONFIG_BT_NIMBLE_MSYS_1_BLOCK_COUNT=100 CONFIG_BT_NIMBLE_LOG_LEVEL_WARNING=y diff --git a/examples/bluetooth/nimble/ble_phy/phy_cent/main/main.c b/examples/bluetooth/nimble/ble_phy/phy_cent/main/main.c index 7181eaedc58..ccb57c124b9 100644 --- a/examples/bluetooth/nimble/ble_phy/phy_cent/main/main.c +++ b/examples/bluetooth/nimble/ble_phy/phy_cent/main/main.c @@ -235,10 +235,6 @@ ext_blecent_should_connect(const struct ble_gap_ext_disc_desc *disc) int offset = 0; int ad_struct_len = 0; uint8_t test_addr[6]; - uint32_t peer_addr[6]; - - memset(peer_addr, 0x0, sizeof peer_addr); - if (disc->legacy_event_type != BLE_HCI_ADV_RPT_EVTYPE_ADV_IND && disc->legacy_event_type != BLE_HCI_ADV_RPT_EVTYPE_DIR_IND) { return 0; @@ -246,13 +242,10 @@ ext_blecent_should_connect(const struct ble_gap_ext_disc_desc *disc) if (strlen(CONFIG_EXAMPLE_PEER_ADDR) && (strncmp(CONFIG_EXAMPLE_PEER_ADDR, "ADDR_ANY", strlen("ADDR_ANY")) != 0)) { ESP_LOGI(tag, "Peer address from menuconfig: %s", CONFIG_EXAMPLE_PEER_ADDR); /* Convert string to address */ - sscanf(CONFIG_EXAMPLE_PEER_ADDR, "%lx:%lx:%lx:%lx:%lx:%lx", - &peer_addr[5], &peer_addr[4], &peer_addr[3], - &peer_addr[2], &peer_addr[1], &peer_addr[0]); - - /* Conversion */ - for (int i=0; i<6; i++) { - test_addr[5 - i] = (uint8_t )peer_addr[i]; + uint8_t parsed_addr[6]; + peer_addr_parse(CONFIG_EXAMPLE_PEER_ADDR, parsed_addr); + for (int i = 0; i < 6; i++) { + test_addr[5 - i] = parsed_addr[i]; } if (memcmp(test_addr, disc->addr.val, sizeof(disc->addr.val)) != 0) { @@ -472,10 +465,6 @@ blecent_on_sync(void) int ii, rc; uint8_t all_phy; uint8_t test_addr[6]; - uint32_t peer_addr[6]; - - memset(peer_addr, 0x0, sizeof peer_addr); - /* Make sure we have proper identity address set (public preferred) */ rc = ble_hs_util_ensure_addr(0); assert(rc == 0); @@ -487,15 +476,7 @@ blecent_on_sync(void) if (strlen(CONFIG_EXAMPLE_PEER_ADDR) && (strncmp(CONFIG_EXAMPLE_PEER_ADDR, "ADDR_ANY", strlen("ADDR_ANY")) != 0)) { /* User wants to connect on 2M or coded phy directly */ - sscanf(CONFIG_EXAMPLE_PEER_ADDR, "%lx:%lx:%lx:%lx:%lx:%lx", - &peer_addr[5], &peer_addr[4], &peer_addr[3], - &peer_addr[2], &peer_addr[1], &peer_addr[0]); - - /* Conversion */ - for (int i=0; i<6; i++) { - test_addr[i] = (uint8_t )peer_addr[i]; - } - + peer_addr_parse(CONFIG_EXAMPLE_PEER_ADDR, test_addr); for(ii = 0 ;ii < 6; ii++) conn_addr.val[ii] = test_addr[ii]; diff --git a/examples/bluetooth/nimble/ble_proximity_sensor/proximity_sensor_cent/main/main.c b/examples/bluetooth/nimble/ble_proximity_sensor/proximity_sensor_cent/main/main.c index 1223715a026..67a6e1ecc53 100644 --- a/examples/bluetooth/nimble/ble_proximity_sensor/proximity_sensor_cent/main/main.c +++ b/examples/bluetooth/nimble/ble_proximity_sensor/proximity_sensor_cent/main/main.c @@ -228,10 +228,6 @@ ext_ble_prox_cent_should_connect(const struct ble_gap_ext_disc_desc *disc) int offset = 0; int ad_struct_len = 0; uint8_t test_addr[6]; - uint32_t peer_addr[6]; - - memset(peer_addr, 0x0, sizeof peer_addr); - if (disc->legacy_event_type != BLE_HCI_ADV_RPT_EVTYPE_ADV_IND && disc->legacy_event_type != BLE_HCI_ADV_RPT_EVTYPE_DIR_IND) { return 0; @@ -239,13 +235,10 @@ ext_ble_prox_cent_should_connect(const struct ble_gap_ext_disc_desc *disc) if (strlen(CONFIG_EXAMPLE_PEER_ADDR) && (strncmp(CONFIG_EXAMPLE_PEER_ADDR, "ADDR_ANY", strlen ("ADDR_ANY")) != 0)) { ESP_LOGI(tag, "Peer address from menuconfig: %s", CONFIG_EXAMPLE_PEER_ADDR); /* Convert string to address */ - sscanf(CONFIG_EXAMPLE_PEER_ADDR, "%lx:%lx:%lx:%lx:%lx:%lx", - &peer_addr[5], &peer_addr[4], &peer_addr[3], - &peer_addr[2], &peer_addr[1], &peer_addr[0]); - - /* Conversion */ - for (int i=0; i<6; i++) { - test_addr[5 - i] = (uint8_t )peer_addr[i]; + uint8_t parsed_addr[6]; + peer_addr_parse(CONFIG_EXAMPLE_PEER_ADDR, parsed_addr); + for (int i = 0; i < 6; i++) { + test_addr[5 - i] = parsed_addr[i]; } if (memcmp(test_addr, disc->addr.val, sizeof(disc->addr.val)) != 0) { @@ -284,10 +277,6 @@ ble_prox_cent_should_connect(const struct ble_gap_disc_desc *disc) int rc; int i; uint8_t test_addr[6]; - uint32_t peer_addr[6]; - - memset(peer_addr, 0x0, sizeof peer_addr); - /* The device has to be advertising connectability. */ if (disc->event_type != BLE_HCI_ADV_RPT_EVTYPE_ADV_IND && disc->event_type != BLE_HCI_ADV_RPT_EVTYPE_DIR_IND) { @@ -303,15 +292,7 @@ ble_prox_cent_should_connect(const struct ble_gap_disc_desc *disc) if (strlen(CONFIG_EXAMPLE_PEER_ADDR) && (strncmp(CONFIG_EXAMPLE_PEER_ADDR, "ADDR_ANY", strlen("ADDR_ANY")) != 0)) { ESP_LOGI(tag, "Peer address from menuconfig: %s", CONFIG_EXAMPLE_PEER_ADDR); /* Convert string to address */ - sscanf(CONFIG_EXAMPLE_PEER_ADDR, "%lx:%lx:%lx:%lx:%lx:%lx", - &peer_addr[5], &peer_addr[4], &peer_addr[3], - &peer_addr[2], &peer_addr[1], &peer_addr[0]); - - /* Conversion */ - for (int i=0; i<6; i++) { - test_addr[i] = (uint8_t )peer_addr[i]; - } - + peer_addr_parse(CONFIG_EXAMPLE_PEER_ADDR, test_addr); if (memcmp(test_addr, disc->addr.val, sizeof(disc->addr.val)) != 0) { return 0; } diff --git a/examples/bluetooth/nimble/blecent/main/main.c b/examples/bluetooth/nimble/blecent/main/main.c index 0e4cb7066c0..3ac1990f51b 100644 --- a/examples/bluetooth/nimble/blecent/main/main.c +++ b/examples/bluetooth/nimble/blecent/main/main.c @@ -481,10 +481,6 @@ ext_blecent_should_connect(const struct ble_gap_ext_disc_desc *disc) uint32_t *addr_offset; #endif // CONFIG_EXAMPLE_USE_CI_ADDRESS uint8_t test_addr[6]; - uint32_t peer_addr[6]; - - memset(peer_addr, 0x0, sizeof peer_addr); - if (disc->legacy_event_type != BLE_HCI_ADV_RPT_EVTYPE_ADV_IND && disc->legacy_event_type != BLE_HCI_ADV_RPT_EVTYPE_DIR_IND) { return 0; @@ -493,16 +489,9 @@ ext_blecent_should_connect(const struct ble_gap_ext_disc_desc *disc) #if !CONFIG_EXAMPLE_USE_CI_ADDRESS ESP_LOGI(tag, "Peer address from menuconfig: %s", CONFIG_EXAMPLE_PEER_ADDR); /* Convert string to address */ - sscanf(CONFIG_EXAMPLE_PEER_ADDR, "%lx:%lx:%lx:%lx:%lx:%lx", - &peer_addr[5], &peer_addr[4], &peer_addr[3], - &peer_addr[2], &peer_addr[1], &peer_addr[0]); + peer_addr_parse(CONFIG_EXAMPLE_PEER_ADDR, test_addr); #endif - /* Conversion */ - for(int i=0; i<6; i++) { - test_addr[i] = (uint8_t )peer_addr[i]; - } - #if CONFIG_EXAMPLE_USE_CI_ADDRESS addr_offset = (uint32_t *)&test_addr[1]; *addr_offset = atoi(CONFIG_EXAMPLE_PEER_ADDR); @@ -549,10 +538,6 @@ blecent_should_connect(const struct ble_gap_disc_desc *disc) uint32_t *addr_offset; #endif // CONFIG_EXAMPLE_USE_CI_ADDRESS uint8_t test_addr[6]; - uint32_t peer_addr[6]; - - memset(peer_addr, 0x0, sizeof peer_addr); - /* The device has to be advertising connectability. */ if (disc->event_type != BLE_HCI_ADV_RPT_EVTYPE_ADV_IND && disc->event_type != BLE_HCI_ADV_RPT_EVTYPE_DIR_IND) { @@ -569,17 +554,9 @@ blecent_should_connect(const struct ble_gap_disc_desc *disc) ESP_LOGI(tag, "Peer address from menuconfig: %s", CONFIG_EXAMPLE_PEER_ADDR); #if !CONFIG_EXAMPLE_USE_CI_ADDRESS /* Convert string to address */ - sscanf(CONFIG_EXAMPLE_PEER_ADDR, "%lx:%lx:%lx:%lx:%lx:%lx", - &peer_addr[5], &peer_addr[4], &peer_addr[3], - &peer_addr[2], &peer_addr[1], &peer_addr[0]); - printf("peer--> %lx %lx %lx %lx %lx %lx \n", peer_addr[5], peer_addr[4], - peer_addr[3], peer_addr[2], peer_addr[1], peer_addr[0]); + peer_addr_parse(CONFIG_EXAMPLE_PEER_ADDR, test_addr); + printf("peer--> %s\n", addr_str(test_addr)); #endif - /* Conversion */ - for (int i=0; i<6; i++) { - test_addr[i] = (uint8_t )peer_addr[i]; - } - #if CONFIG_EXAMPLE_USE_CI_ADDRESS addr_offset = (uint32_t *)&test_addr[1]; *addr_offset = atoi(CONFIG_EXAMPLE_PEER_ADDR); diff --git a/examples/bluetooth/nimble/common/nimble_central_utils/esp_central.h b/examples/bluetooth/nimble/common/nimble_central_utils/esp_central.h index 45fbb6ab0e7..2bca76cd05e 100644 --- a/examples/bluetooth/nimble/common/nimble_central_utils/esp_central.h +++ b/examples/bluetooth/nimble/common/nimble_central_utils/esp_central.h @@ -15,6 +15,7 @@ extern "C" { #define PEER_ADDR_VAL_SIZE 6 /** Misc. */ +int peer_addr_parse(const char *addr_str, uint8_t addr[PEER_ADDR_VAL_SIZE]); void print_bytes(const uint8_t *bytes, int len); void print_mbuf(const struct os_mbuf *om); void print_mbuf_data(const struct os_mbuf *om); diff --git a/examples/bluetooth/nimble/common/nimble_central_utils/misc.c b/examples/bluetooth/nimble/common/nimble_central_utils/misc.c index 1c89f7e3902..bf936e80b2c 100644 --- a/examples/bluetooth/nimble/common/nimble_central_utils/misc.c +++ b/examples/bluetooth/nimble/common/nimble_central_utils/misc.c @@ -4,7 +4,21 @@ * SPDX-License-Identifier: Unlicense OR CC0-1.0 */ +#include +#include #include "host/ble_hs.h" +#include "esp_central.h" + +int +peer_addr_parse(const char *addr_str, uint8_t addr[PEER_ADDR_VAL_SIZE]) +{ + if (addr_str == NULL) { + return 0; + } + return sscanf(addr_str, "%hhx:%hhx:%hhx:%hhx:%hhx:%hhx", + &addr[5], &addr[4], &addr[3], + &addr[2], &addr[1], &addr[0]); +} /** * Utility function to log an array of bytes. diff --git a/examples/bluetooth/nimble/throughput_app/blecent_throughput/components/cmd_system/cmd_system.c b/examples/bluetooth/nimble/throughput_app/blecent_throughput/components/cmd_system/cmd_system.c index 60a4735a443..ef6ccfda967 100644 --- a/examples/bluetooth/nimble/throughput_app/blecent_throughput/components/cmd_system/cmd_system.c +++ b/examples/bluetooth/nimble/throughput_app/blecent_throughput/components/cmd_system/cmd_system.c @@ -4,6 +4,7 @@ * SPDX-License-Identifier: Apache-2.0 */ +#include #include #include #include @@ -329,7 +330,7 @@ static int light_sleep(int argc, char **argv) uint32_t causes = esp_sleep_get_wakeup_causes(); if (causes & BIT(ESP_SLEEP_WAKEUP_UNDEFINED)) { ESP_LOGI(TAG, "Woke up from: unknown"); - printf("%lx\n", causes); + printf("%" PRIx32 "\n", causes); return 0; } if (causes & BIT(ESP_SLEEP_WAKEUP_GPIO)) { diff --git a/examples/bluetooth/nimble/throughput_app/blecent_throughput/main/gattc.h b/examples/bluetooth/nimble/throughput_app/blecent_throughput/main/gattc.h index 6762d3d42c8..b90f4a998cb 100644 --- a/examples/bluetooth/nimble/throughput_app/blecent_throughput/main/gattc.h +++ b/examples/bluetooth/nimble/throughput_app/blecent_throughput/main/gattc.h @@ -27,7 +27,10 @@ union ble_store_key; #define BLECENT_CHR_UNR_ALERT_STAT_UUID 0x2A45 #define BLECENT_CHR_ALERT_NOT_CTRL_PT 0x2A44 +#define PEER_ADDR_VAL_SIZE 6 + /** Misc. */ +int peer_addr_parse(const char *addr_str, uint8_t addr[PEER_ADDR_VAL_SIZE]); void print_bytes(const uint8_t *bytes, int len); void print_mbuf(const struct os_mbuf *om); char *addr_str(const void *addr); diff --git a/examples/bluetooth/nimble/throughput_app/blecent_throughput/main/main.c b/examples/bluetooth/nimble/throughput_app/blecent_throughput/main/main.c index e95f26045bb..43a4ff8cb70 100644 --- a/examples/bluetooth/nimble/throughput_app/blecent_throughput/main/main.c +++ b/examples/bluetooth/nimble/throughput_app/blecent_throughput/main/main.c @@ -763,7 +763,7 @@ ext_blecent_should_connect(const struct ble_gap_ext_disc_desc *disc) int offset = 0; int ad_struct_len = 0; uint8_t test_addr[6]; - uint32_t peer_addr[6]; + uint8_t parsed_addr[6]; uint8_t phy_uuid_found = 0; if (disc->legacy_event_type != BLE_HCI_ADV_RPT_EVTYPE_ADV_IND && @@ -773,13 +773,9 @@ ext_blecent_should_connect(const struct ble_gap_ext_disc_desc *disc) if (strlen(CONFIG_EXAMPLE_PEER_ADDR) && (strncmp(CONFIG_EXAMPLE_PEER_ADDR, "ADDR_ANY", strlen("ADDR_ANY")) != 0)) { // ESP_LOGI(tag, "Peer address from menuconfig: %s", CONFIG_EXAMPLE_PEER_ADDR); /* Convert string to address */ - sscanf(CONFIG_EXAMPLE_PEER_ADDR, "%lx:%lx:%lx:%lx:%lx:%lx", - &peer_addr[5], &peer_addr[4], &peer_addr[3], - &peer_addr[2], &peer_addr[1], &peer_addr[0]); - - /* Conversion */ - for (int i=0; i<6; i++) { - test_addr[5 - i] = (uint8_t )peer_addr[i]; + peer_addr_parse(CONFIG_EXAMPLE_PEER_ADDR, parsed_addr); + for (int i = 0; i < 6; i++) { + test_addr[5 - i] = parsed_addr[i]; } if (memcmp(test_addr, disc->addr.val, sizeof(disc->addr.val)) != 0) { return 0; @@ -839,10 +835,6 @@ blecent_should_connect(const struct ble_gap_disc_desc *disc) int rc; int i; uint8_t test_addr[6]; - uint32_t peer_addr[6]; - - memset(peer_addr, 0x0, sizeof peer_addr); - rc = ble_hs_adv_parse_fields(&fields, disc->data, disc->length_data); if (rc != 0) { return 0; @@ -851,15 +843,7 @@ blecent_should_connect(const struct ble_gap_disc_desc *disc) if (strlen(CONFIG_EXAMPLE_PEER_ADDR) && (strncmp(CONFIG_EXAMPLE_PEER_ADDR, "ADDR_ANY", strlen("ADDR_ANY")) != 0)) { ESP_LOGI(tag, "Peer address from menuconfig: %s", CONFIG_EXAMPLE_PEER_ADDR); /* Convert string to address */ - sscanf(CONFIG_EXAMPLE_PEER_ADDR, "%lx:%lx:%lx:%lx:%lx:%lx", - &peer_addr[5], &peer_addr[4], &peer_addr[3], - &peer_addr[2], &peer_addr[1], &peer_addr[0]); - - /* Conversion */ - for (int i=0; i<6; i++) { - test_addr[i] = (uint8_t )peer_addr[i]; - } - + peer_addr_parse(CONFIG_EXAMPLE_PEER_ADDR, test_addr); if (memcmp(test_addr, disc->addr.val, sizeof(disc->addr.val)) != 0) { return 0; } diff --git a/examples/bluetooth/nimble/throughput_app/blecent_throughput/main/misc.c b/examples/bluetooth/nimble/throughput_app/blecent_throughput/main/misc.c index 4492ba84df7..1b8ba43a4b6 100644 --- a/examples/bluetooth/nimble/throughput_app/blecent_throughput/main/misc.c +++ b/examples/bluetooth/nimble/throughput_app/blecent_throughput/main/misc.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2015-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2015-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -11,6 +11,17 @@ #include "host/ble_uuid.h" #include "gattc.h" +int +peer_addr_parse(const char *addr_str, uint8_t addr[PEER_ADDR_VAL_SIZE]) +{ + if (addr_str == NULL) { + return 0; + } + return sscanf(addr_str, "%hhx:%hhx:%hhx:%hhx:%hhx:%hhx", + &addr[5], &addr[4], &addr[3], + &addr[2], &addr[1], &addr[0]); +} + /** * Utility function to log an array of bytes. */ From f783d3a7ebd5b85c5c6341c91ea871f9b32ac066 Mon Sep 17 00:00:00 2001 From: Rahul Tank Date: Thu, 4 Jun 2026 12:35:23 +0530 Subject: [PATCH 09/13] fix(nimble): Deinit NimBLE stop event in host task --- components/bt/host/nimble/nimble | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/bt/host/nimble/nimble b/components/bt/host/nimble/nimble index f97c9c55f61..46114cf2d23 160000 --- a/components/bt/host/nimble/nimble +++ b/components/bt/host/nimble/nimble @@ -1 +1 @@ -Subproject commit f97c9c55f6146739417b96dc5b94881bfe1ce902 +Subproject commit 46114cf2d2356b0de516f939b55a418355063c66 From dbe2e860da9806934ae37022da0d9c60a9cbdedf Mon Sep 17 00:00:00 2001 From: Sumeet Singh Date: Thu, 4 Jun 2026 17:54:47 +0530 Subject: [PATCH 10/13] fix(nimble): Change remaining vars from Static to Dynamic (v6.0) --- components/bt/host/nimble/nimble | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/bt/host/nimble/nimble b/components/bt/host/nimble/nimble index 46114cf2d23..139cada0ae9 160000 --- a/components/bt/host/nimble/nimble +++ b/components/bt/host/nimble/nimble @@ -1 +1 @@ -Subproject commit 46114cf2d2356b0de516f939b55a418355063c66 +Subproject commit 139cada0ae932957fa06ba37d17e3c9c2c95c773 From 13ce6692eb70666e5463b4690fbb5777bfc5ca6f Mon Sep 17 00:00:00 2001 From: Rahul Tank Date: Mon, 15 Jun 2026 12:30:13 +0530 Subject: [PATCH 11/13] fix(nimble): Gaurd ble_hs_log_mbuf/flat_buf against non-DEBUG log level --- components/bt/host/nimble/nimble | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/bt/host/nimble/nimble b/components/bt/host/nimble/nimble index 139cada0ae9..7b2c4ce380d 160000 --- a/components/bt/host/nimble/nimble +++ b/components/bt/host/nimble/nimble @@ -1 +1 @@ -Subproject commit 139cada0ae932957fa06ba37d17e3c9c2c95c773 +Subproject commit 7b2c4ce380d8c7a79a977ebeedce5f3786b43042 From b0f278ec199ebbdab833feedaaf0ec61479ad448 Mon Sep 17 00:00:00 2001 From: Astha Verma Date: Mon, 6 Apr 2026 11:39:55 +0530 Subject: [PATCH 12/13] fix(nimble): Added l2cap_coc throughput examples --- components/bt/host/nimble/nimble | 2 +- examples/bluetooth/.build-test-rules.yml | 24 +- .../bluetooth/nimble/throughput_app/README.md | 56 +- .../blecent_throughput/CMakeLists.txt | 0 .../{ => gatt}/blecent_throughput/README.md | 0 .../components/cmd_system/CMakeLists.txt | 0 .../components/cmd_system/cmd_system.c | 0 .../components/cmd_system/cmd_system.h | 0 .../components/cmd_system/component.mk | 0 .../blecent_throughput/main/CMakeLists.txt | 0 .../blecent_throughput/main/Kconfig.projbuild | 0 .../blecent_throughput/main/gattc.h | 0 .../{ => gatt}/blecent_throughput/main/main.c | 0 .../{ => gatt}/blecent_throughput/main/misc.c | 0 .../{ => gatt}/blecent_throughput/main/peer.c | 0 .../{ => gatt}/blecent_throughput/main/scli.c | 0 .../blecent_throughput/sdkconfig.defaults | 3 - .../sdkconfig.defaults.esp32c6 | 0 .../bleprph_throughput/CMakeLists.txt | 0 .../{ => gatt}/bleprph_throughput/README.md | 0 .../bleprph_throughput/main/CMakeLists.txt | 0 .../bleprph_throughput/main/Kconfig.projbuild | 0 .../bleprph_throughput/main/gatt_svr.c | 0 .../bleprph_throughput/main/gatts_sens.h | 0 .../{ => gatt}/bleprph_throughput/main/main.c | 0 .../bleprph_throughput/sdkconfig.defaults | 3 - .../sdkconfig.defaults.esp32c6 | 0 .../l2cap_coc/l2cap_coc_cent/CMakeLists.txt | 5 + .../l2cap_coc/l2cap_coc_cent/README.md | 94 +++ .../l2cap_coc_cent/main/CMakeLists.txt | 3 + .../l2cap_coc_cent/main/Kconfig.projbuild | 67 ++ .../l2cap_coc_cent/main/idf_component.yml | 3 + .../l2cap_coc/l2cap_coc_cent/main/main.c | 738 ++++++++++++++++++ .../l2cap_coc_cent/sdkconfig.defaults | 16 + .../l2cap_coc_cent/sdkconfig.defaults.esp32 | 4 + .../l2cap_coc_cent/sdkconfig.defaults.esp32c2 | 6 + .../l2cap_coc_cent/sdkconfig.defaults.esp32c3 | 4 + .../l2cap_coc_cent/sdkconfig.defaults.esp32c6 | 3 + .../l2cap_coc_cent/sdkconfig.defaults.esp32h2 | 3 + .../l2cap_coc/l2cap_coc_prph/CMakeLists.txt | 5 + .../l2cap_coc/l2cap_coc_prph/README.md | 77 ++ .../l2cap_coc_prph/main/CMakeLists.txt | 3 + .../l2cap_coc_prph/main/Kconfig.projbuild | 23 + .../l2cap_coc/l2cap_coc_prph/main/main.c | 445 +++++++++++ .../l2cap_coc_prph/sdkconfig.defaults | 15 + .../l2cap_coc_prph/sdkconfig.defaults.esp32 | 4 + .../l2cap_coc_prph/sdkconfig.defaults.esp32c2 | 7 + .../l2cap_coc_prph/sdkconfig.defaults.esp32c3 | 6 + .../l2cap_coc_prph/sdkconfig.defaults.esp32c6 | 2 + .../l2cap_coc_prph/sdkconfig.defaults.esp32h2 | 2 + 50 files changed, 1603 insertions(+), 20 deletions(-) rename examples/bluetooth/nimble/throughput_app/{ => gatt}/blecent_throughput/CMakeLists.txt (100%) rename examples/bluetooth/nimble/throughput_app/{ => gatt}/blecent_throughput/README.md (100%) rename examples/bluetooth/nimble/throughput_app/{ => gatt}/blecent_throughput/components/cmd_system/CMakeLists.txt (100%) rename examples/bluetooth/nimble/throughput_app/{ => gatt}/blecent_throughput/components/cmd_system/cmd_system.c (100%) rename examples/bluetooth/nimble/throughput_app/{ => gatt}/blecent_throughput/components/cmd_system/cmd_system.h (100%) rename examples/bluetooth/nimble/throughput_app/{ => gatt}/blecent_throughput/components/cmd_system/component.mk (100%) rename examples/bluetooth/nimble/throughput_app/{ => gatt}/blecent_throughput/main/CMakeLists.txt (100%) rename examples/bluetooth/nimble/throughput_app/{ => gatt}/blecent_throughput/main/Kconfig.projbuild (100%) rename examples/bluetooth/nimble/throughput_app/{ => gatt}/blecent_throughput/main/gattc.h (100%) rename examples/bluetooth/nimble/throughput_app/{ => gatt}/blecent_throughput/main/main.c (100%) rename examples/bluetooth/nimble/throughput_app/{ => gatt}/blecent_throughput/main/misc.c (100%) rename examples/bluetooth/nimble/throughput_app/{ => gatt}/blecent_throughput/main/peer.c (100%) rename examples/bluetooth/nimble/throughput_app/{ => gatt}/blecent_throughput/main/scli.c (100%) rename examples/bluetooth/nimble/throughput_app/{ => gatt}/blecent_throughput/sdkconfig.defaults (91%) rename examples/bluetooth/nimble/throughput_app/{ => gatt}/blecent_throughput/sdkconfig.defaults.esp32c6 (100%) rename examples/bluetooth/nimble/throughput_app/{ => gatt}/bleprph_throughput/CMakeLists.txt (100%) rename examples/bluetooth/nimble/throughput_app/{ => gatt}/bleprph_throughput/README.md (100%) rename examples/bluetooth/nimble/throughput_app/{ => gatt}/bleprph_throughput/main/CMakeLists.txt (100%) rename examples/bluetooth/nimble/throughput_app/{ => gatt}/bleprph_throughput/main/Kconfig.projbuild (100%) rename examples/bluetooth/nimble/throughput_app/{ => gatt}/bleprph_throughput/main/gatt_svr.c (100%) rename examples/bluetooth/nimble/throughput_app/{ => gatt}/bleprph_throughput/main/gatts_sens.h (100%) rename examples/bluetooth/nimble/throughput_app/{ => gatt}/bleprph_throughput/main/main.c (100%) rename examples/bluetooth/nimble/throughput_app/{ => gatt}/bleprph_throughput/sdkconfig.defaults (90%) rename examples/bluetooth/nimble/throughput_app/{ => gatt}/bleprph_throughput/sdkconfig.defaults.esp32c6 (100%) create mode 100644 examples/bluetooth/nimble/throughput_app/l2cap_coc/l2cap_coc_cent/CMakeLists.txt create mode 100644 examples/bluetooth/nimble/throughput_app/l2cap_coc/l2cap_coc_cent/README.md create mode 100644 examples/bluetooth/nimble/throughput_app/l2cap_coc/l2cap_coc_cent/main/CMakeLists.txt create mode 100644 examples/bluetooth/nimble/throughput_app/l2cap_coc/l2cap_coc_cent/main/Kconfig.projbuild create mode 100644 examples/bluetooth/nimble/throughput_app/l2cap_coc/l2cap_coc_cent/main/idf_component.yml create mode 100644 examples/bluetooth/nimble/throughput_app/l2cap_coc/l2cap_coc_cent/main/main.c create mode 100644 examples/bluetooth/nimble/throughput_app/l2cap_coc/l2cap_coc_cent/sdkconfig.defaults create mode 100644 examples/bluetooth/nimble/throughput_app/l2cap_coc/l2cap_coc_cent/sdkconfig.defaults.esp32 create mode 100644 examples/bluetooth/nimble/throughput_app/l2cap_coc/l2cap_coc_cent/sdkconfig.defaults.esp32c2 create mode 100644 examples/bluetooth/nimble/throughput_app/l2cap_coc/l2cap_coc_cent/sdkconfig.defaults.esp32c3 create mode 100644 examples/bluetooth/nimble/throughput_app/l2cap_coc/l2cap_coc_cent/sdkconfig.defaults.esp32c6 create mode 100644 examples/bluetooth/nimble/throughput_app/l2cap_coc/l2cap_coc_cent/sdkconfig.defaults.esp32h2 create mode 100644 examples/bluetooth/nimble/throughput_app/l2cap_coc/l2cap_coc_prph/CMakeLists.txt create mode 100644 examples/bluetooth/nimble/throughput_app/l2cap_coc/l2cap_coc_prph/README.md create mode 100644 examples/bluetooth/nimble/throughput_app/l2cap_coc/l2cap_coc_prph/main/CMakeLists.txt create mode 100644 examples/bluetooth/nimble/throughput_app/l2cap_coc/l2cap_coc_prph/main/Kconfig.projbuild create mode 100644 examples/bluetooth/nimble/throughput_app/l2cap_coc/l2cap_coc_prph/main/main.c create mode 100644 examples/bluetooth/nimble/throughput_app/l2cap_coc/l2cap_coc_prph/sdkconfig.defaults create mode 100644 examples/bluetooth/nimble/throughput_app/l2cap_coc/l2cap_coc_prph/sdkconfig.defaults.esp32 create mode 100644 examples/bluetooth/nimble/throughput_app/l2cap_coc/l2cap_coc_prph/sdkconfig.defaults.esp32c2 create mode 100644 examples/bluetooth/nimble/throughput_app/l2cap_coc/l2cap_coc_prph/sdkconfig.defaults.esp32c3 create mode 100644 examples/bluetooth/nimble/throughput_app/l2cap_coc/l2cap_coc_prph/sdkconfig.defaults.esp32c6 create mode 100644 examples/bluetooth/nimble/throughput_app/l2cap_coc/l2cap_coc_prph/sdkconfig.defaults.esp32h2 diff --git a/components/bt/host/nimble/nimble b/components/bt/host/nimble/nimble index 7b2c4ce380d..685675c0128 160000 --- a/components/bt/host/nimble/nimble +++ b/components/bt/host/nimble/nimble @@ -1 +1 @@ -Subproject commit 7b2c4ce380d8c7a79a977ebeedce5f3786b43042 +Subproject commit 685675c0128deafdd201c9eb82e61d227364646c diff --git a/examples/bluetooth/.build-test-rules.yml b/examples/bluetooth/.build-test-rules.yml index 285a481ee1d..7cff065930e 100644 --- a/examples/bluetooth/.build-test-rules.yml +++ b/examples/bluetooth/.build-test-rules.yml @@ -428,14 +428,10 @@ examples/bluetooth/nimble/throughput_app: <<: *bt_default_depends disable: - if: SOC_BLE_SUPPORTED != 1 - depends_components+: - - esp_driver_gpio - - esp_driver_uart depends_filepatterns: - examples/bluetooth/nimble/common/**/* - - examples/bluetooth/nimble/throughput_app/blecent_throughput/components/**/* -examples/bluetooth/nimble/throughput_app/blecent_throughput: +examples/bluetooth/nimble/throughput_app/gatt/blecent_throughput: <<: *bt_default_depends disable: - if: SOC_BLE_SUPPORTED != 1 @@ -444,4 +440,20 @@ examples/bluetooth/nimble/throughput_app/blecent_throughput: - esp_driver_uart depends_filepatterns: - examples/bluetooth/nimble/common/**/* - - examples/bluetooth/nimble/throughput_app/blecent_throughput/components/**/* + - examples/bluetooth/nimble/throughput_app/gatt/blecent_throughput/components/**/* + +examples/bluetooth/nimble/throughput_app/l2cap_coc/l2cap_coc_cent: + <<: *bt_default_depends + disable: + - if: SOC_BLE_SUPPORTED != 1 + depends_filepatterns: + - examples/bluetooth/nimble/common/**/* + - examples/bluetooth/nimble/throughput_app/l2cap_coc/l2cap_coc_cent/**/* + +examples/bluetooth/nimble/throughput_app/l2cap_coc/l2cap_coc_prph: + <<: *bt_default_depends + disable: + - if: SOC_BLE_SUPPORTED != 1 + depends_filepatterns: + - examples/bluetooth/nimble/common/**/* + - examples/bluetooth/nimble/throughput_app/l2cap_coc/l2cap_coc_prph/**/* diff --git a/examples/bluetooth/nimble/throughput_app/README.md b/examples/bluetooth/nimble/throughput_app/README.md index f19b438111b..4e209860bde 100644 --- a/examples/bluetooth/nimble/throughput_app/README.md +++ b/examples/bluetooth/nimble/throughput_app/README.md @@ -1,22 +1,38 @@ # Throughput Demo Examples -There are two example folders inside this `throughput_app`: `bleprph_throughput` (peripheral) and `blecent_throughput` (central). These examples demonstrate BLE GATT throughput measurement using NimBLE on ESP32. Two ESP32 boards are needed to run this demo. The `blecent_throughput` example has CLI support to select GATT operation from READ/WRITE/NOTIFY and configure connection parameters at runtime. More details can be found in respective READMEs. +This folder contains BLE throughput measurement examples for NimBLE on ESP32, organized into two sub-folders by protocol: -## Using the Examples +``` +throughput_app/ +├── gatt/ +│ ├── blecent_throughput/ — GATT central (initiator) +│ └── bleprph_throughput/ — GATT peripheral (responder) +└── l2cap_coc/ + ├── l2cap_coc_cent/ — L2CAP CoC central (sender) + └── l2cap_coc_prph/ — L2CAP CoC peripheral (receiver) +``` + +--- + +## gatt/ + +There are two example folders inside `gatt/`: `bleprph_throughput` (peripheral) and `blecent_throughput` (central). These examples demonstrate BLE GATT throughput measurement using NimBLE on ESP32. Two ESP32 boards are needed to run this demo. The `blecent_throughput` example has CLI support to select GATT operation from READ/WRITE/NOTIFY and configure connection parameters at runtime. More details can be found in respective READMEs. + +### Using the Examples Build and flash two ESP32 boards with `bleprph_throughput` and `blecent_throughput` examples. The central automatically scans and connects to the peripheral based on device name (`nimble_prph`). After connection, the user may optionally configure connection parameters (`MTU`, `connection interval`, `latency`, `supervision timeout`, `connection event length`). Then the user specifies the throughput test type (`read`, `write` or `notify`) and test duration in seconds. Below are sample throughput numbers for a 60-second test run (MTU = 512, conn itvl = 7.5ms, DLE = 251 bytes, 1M PHY): |GATT Method | Measurement Time | Application Throughput| -|--- | --- | ---| -|NOTIFY | 60 seconds | ~340 Kbps| -|READ | 60 seconds | ~200 Kbps| -|WRITE | 60 seconds | ~500 Kbps| +|----------- | ---------------- | ----------------------| +|NOTIFY | 60 seconds | ~340 Kbps | +|READ | 60 seconds | ~200 Kbps | +|WRITE | 60 seconds | ~500 Kbps | The notify throughput output is displayed on the `bleprph_throughput` console, while read/write throughput results are shown on the `blecent_throughput` console. -## Throughput Optimization +### Throughput Optimization The following parameters have the most significant impact on throughput: @@ -35,3 +51,29 @@ The following parameters have the most significant impact on throughput: 7. **MSYS Buffer Count**: Both peripheral and central are configured with 50 MSYS blocks (`CONFIG_BT_NIMBLE_MSYS_1_BLOCK_COUNT=50`) to provide sufficient buffer space for high-throughput operations. 8. **PHY**: On BLE 5.0 supported chipsets, 2M PHY can be selected to double the air data rate. Use Extended Advertising mode and specify PHY in the throughput CLI command. + +--- + +## l2cap_coc/ + +There are two example folders inside `l2cap_coc/`: `l2cap_coc_prph` (peripheral/receiver) and `l2cap_coc_cent` (central/sender). These examples demonstrate BLE L2CAP Connection-Oriented Channel (CoC) throughput measurement using NimBLE on ESP32. Two ESP32 boards are needed to run this demo. More details can be found in respective READMEs. + +### How It Works + +L2CAP CoC provides a direct channel between two devices without the ATT/GATT overhead, making it more efficient for bulk data transfer. + +- The peripheral (`l2cap_coc_prph`) advertises with UUID 0x1812 and registers an L2CAP CoC server on PSM 0x1002. On connection it pre-grants receive credits to the central so the central can pipeline multiple SDUs immediately. +- The central (`l2cap_coc_cent`) scans for UUID 0x1812, connects, enables Data Length Extension (DLE), then opens an L2CAP CoC channel and continuously sends SDUs to the peripheral. +- Data flows **central → peripheral**. The central controls PHY selection, cycling through all enabled PHYs (1M, 2M, Coded S2, Coded S8) in sequence and printing a TX throughput summary after each test interval. +- The peripheral tracks RX throughput per PHY, printing a per-PHY summary box each time the central switches PHY, and a live per-second RX rate while data is flowing. + +### Using the Examples + +Build and flash two ESP32 boards with `l2cap_coc_prph` and `l2cap_coc_cent` examples. The central automatically scans and connects — no user input required. The test runs continuously, cycling through enabled PHYs. + +Below are sample throughput numbers (MTU = 2048, DLE = 251 bytes, conn itvl = 7.5ms, ESP32-C6): + +| PHY | Measurement Time | Application Throughput | +|-----|-----------------|------------------------| +| 1M | 8 seconds | ~741 kbps | +| 2M | 8 seconds | ~1310 kbps | diff --git a/examples/bluetooth/nimble/throughput_app/blecent_throughput/CMakeLists.txt b/examples/bluetooth/nimble/throughput_app/gatt/blecent_throughput/CMakeLists.txt similarity index 100% rename from examples/bluetooth/nimble/throughput_app/blecent_throughput/CMakeLists.txt rename to examples/bluetooth/nimble/throughput_app/gatt/blecent_throughput/CMakeLists.txt diff --git a/examples/bluetooth/nimble/throughput_app/blecent_throughput/README.md b/examples/bluetooth/nimble/throughput_app/gatt/blecent_throughput/README.md similarity index 100% rename from examples/bluetooth/nimble/throughput_app/blecent_throughput/README.md rename to examples/bluetooth/nimble/throughput_app/gatt/blecent_throughput/README.md diff --git a/examples/bluetooth/nimble/throughput_app/blecent_throughput/components/cmd_system/CMakeLists.txt b/examples/bluetooth/nimble/throughput_app/gatt/blecent_throughput/components/cmd_system/CMakeLists.txt similarity index 100% rename from examples/bluetooth/nimble/throughput_app/blecent_throughput/components/cmd_system/CMakeLists.txt rename to examples/bluetooth/nimble/throughput_app/gatt/blecent_throughput/components/cmd_system/CMakeLists.txt diff --git a/examples/bluetooth/nimble/throughput_app/blecent_throughput/components/cmd_system/cmd_system.c b/examples/bluetooth/nimble/throughput_app/gatt/blecent_throughput/components/cmd_system/cmd_system.c similarity index 100% rename from examples/bluetooth/nimble/throughput_app/blecent_throughput/components/cmd_system/cmd_system.c rename to examples/bluetooth/nimble/throughput_app/gatt/blecent_throughput/components/cmd_system/cmd_system.c diff --git a/examples/bluetooth/nimble/throughput_app/blecent_throughput/components/cmd_system/cmd_system.h b/examples/bluetooth/nimble/throughput_app/gatt/blecent_throughput/components/cmd_system/cmd_system.h similarity index 100% rename from examples/bluetooth/nimble/throughput_app/blecent_throughput/components/cmd_system/cmd_system.h rename to examples/bluetooth/nimble/throughput_app/gatt/blecent_throughput/components/cmd_system/cmd_system.h diff --git a/examples/bluetooth/nimble/throughput_app/blecent_throughput/components/cmd_system/component.mk b/examples/bluetooth/nimble/throughput_app/gatt/blecent_throughput/components/cmd_system/component.mk similarity index 100% rename from examples/bluetooth/nimble/throughput_app/blecent_throughput/components/cmd_system/component.mk rename to examples/bluetooth/nimble/throughput_app/gatt/blecent_throughput/components/cmd_system/component.mk diff --git a/examples/bluetooth/nimble/throughput_app/blecent_throughput/main/CMakeLists.txt b/examples/bluetooth/nimble/throughput_app/gatt/blecent_throughput/main/CMakeLists.txt similarity index 100% rename from examples/bluetooth/nimble/throughput_app/blecent_throughput/main/CMakeLists.txt rename to examples/bluetooth/nimble/throughput_app/gatt/blecent_throughput/main/CMakeLists.txt diff --git a/examples/bluetooth/nimble/throughput_app/blecent_throughput/main/Kconfig.projbuild b/examples/bluetooth/nimble/throughput_app/gatt/blecent_throughput/main/Kconfig.projbuild similarity index 100% rename from examples/bluetooth/nimble/throughput_app/blecent_throughput/main/Kconfig.projbuild rename to examples/bluetooth/nimble/throughput_app/gatt/blecent_throughput/main/Kconfig.projbuild diff --git a/examples/bluetooth/nimble/throughput_app/blecent_throughput/main/gattc.h b/examples/bluetooth/nimble/throughput_app/gatt/blecent_throughput/main/gattc.h similarity index 100% rename from examples/bluetooth/nimble/throughput_app/blecent_throughput/main/gattc.h rename to examples/bluetooth/nimble/throughput_app/gatt/blecent_throughput/main/gattc.h diff --git a/examples/bluetooth/nimble/throughput_app/blecent_throughput/main/main.c b/examples/bluetooth/nimble/throughput_app/gatt/blecent_throughput/main/main.c similarity index 100% rename from examples/bluetooth/nimble/throughput_app/blecent_throughput/main/main.c rename to examples/bluetooth/nimble/throughput_app/gatt/blecent_throughput/main/main.c diff --git a/examples/bluetooth/nimble/throughput_app/blecent_throughput/main/misc.c b/examples/bluetooth/nimble/throughput_app/gatt/blecent_throughput/main/misc.c similarity index 100% rename from examples/bluetooth/nimble/throughput_app/blecent_throughput/main/misc.c rename to examples/bluetooth/nimble/throughput_app/gatt/blecent_throughput/main/misc.c diff --git a/examples/bluetooth/nimble/throughput_app/blecent_throughput/main/peer.c b/examples/bluetooth/nimble/throughput_app/gatt/blecent_throughput/main/peer.c similarity index 100% rename from examples/bluetooth/nimble/throughput_app/blecent_throughput/main/peer.c rename to examples/bluetooth/nimble/throughput_app/gatt/blecent_throughput/main/peer.c diff --git a/examples/bluetooth/nimble/throughput_app/blecent_throughput/main/scli.c b/examples/bluetooth/nimble/throughput_app/gatt/blecent_throughput/main/scli.c similarity index 100% rename from examples/bluetooth/nimble/throughput_app/blecent_throughput/main/scli.c rename to examples/bluetooth/nimble/throughput_app/gatt/blecent_throughput/main/scli.c diff --git a/examples/bluetooth/nimble/throughput_app/blecent_throughput/sdkconfig.defaults b/examples/bluetooth/nimble/throughput_app/gatt/blecent_throughput/sdkconfig.defaults similarity index 91% rename from examples/bluetooth/nimble/throughput_app/blecent_throughput/sdkconfig.defaults rename to examples/bluetooth/nimble/throughput_app/gatt/blecent_throughput/sdkconfig.defaults index c272b4700f4..e65b4a19cb7 100644 --- a/examples/bluetooth/nimble/throughput_app/blecent_throughput/sdkconfig.defaults +++ b/examples/bluetooth/nimble/throughput_app/gatt/blecent_throughput/sdkconfig.defaults @@ -5,9 +5,6 @@ # BT config (universal across all ESP32 variants) # CONFIG_BT_ENABLED=y -CONFIG_BTDM_CTRL_MODE_BLE_ONLY=y -CONFIG_BTDM_CTRL_MODE_BR_EDR_ONLY=n -CONFIG_BTDM_CTRL_MODE_BTDM=n CONFIG_BT_BLUEDROID_ENABLED=n CONFIG_BT_NIMBLE_ENABLED=y diff --git a/examples/bluetooth/nimble/throughput_app/blecent_throughput/sdkconfig.defaults.esp32c6 b/examples/bluetooth/nimble/throughput_app/gatt/blecent_throughput/sdkconfig.defaults.esp32c6 similarity index 100% rename from examples/bluetooth/nimble/throughput_app/blecent_throughput/sdkconfig.defaults.esp32c6 rename to examples/bluetooth/nimble/throughput_app/gatt/blecent_throughput/sdkconfig.defaults.esp32c6 diff --git a/examples/bluetooth/nimble/throughput_app/bleprph_throughput/CMakeLists.txt b/examples/bluetooth/nimble/throughput_app/gatt/bleprph_throughput/CMakeLists.txt similarity index 100% rename from examples/bluetooth/nimble/throughput_app/bleprph_throughput/CMakeLists.txt rename to examples/bluetooth/nimble/throughput_app/gatt/bleprph_throughput/CMakeLists.txt diff --git a/examples/bluetooth/nimble/throughput_app/bleprph_throughput/README.md b/examples/bluetooth/nimble/throughput_app/gatt/bleprph_throughput/README.md similarity index 100% rename from examples/bluetooth/nimble/throughput_app/bleprph_throughput/README.md rename to examples/bluetooth/nimble/throughput_app/gatt/bleprph_throughput/README.md diff --git a/examples/bluetooth/nimble/throughput_app/bleprph_throughput/main/CMakeLists.txt b/examples/bluetooth/nimble/throughput_app/gatt/bleprph_throughput/main/CMakeLists.txt similarity index 100% rename from examples/bluetooth/nimble/throughput_app/bleprph_throughput/main/CMakeLists.txt rename to examples/bluetooth/nimble/throughput_app/gatt/bleprph_throughput/main/CMakeLists.txt diff --git a/examples/bluetooth/nimble/throughput_app/bleprph_throughput/main/Kconfig.projbuild b/examples/bluetooth/nimble/throughput_app/gatt/bleprph_throughput/main/Kconfig.projbuild similarity index 100% rename from examples/bluetooth/nimble/throughput_app/bleprph_throughput/main/Kconfig.projbuild rename to examples/bluetooth/nimble/throughput_app/gatt/bleprph_throughput/main/Kconfig.projbuild diff --git a/examples/bluetooth/nimble/throughput_app/bleprph_throughput/main/gatt_svr.c b/examples/bluetooth/nimble/throughput_app/gatt/bleprph_throughput/main/gatt_svr.c similarity index 100% rename from examples/bluetooth/nimble/throughput_app/bleprph_throughput/main/gatt_svr.c rename to examples/bluetooth/nimble/throughput_app/gatt/bleprph_throughput/main/gatt_svr.c diff --git a/examples/bluetooth/nimble/throughput_app/bleprph_throughput/main/gatts_sens.h b/examples/bluetooth/nimble/throughput_app/gatt/bleprph_throughput/main/gatts_sens.h similarity index 100% rename from examples/bluetooth/nimble/throughput_app/bleprph_throughput/main/gatts_sens.h rename to examples/bluetooth/nimble/throughput_app/gatt/bleprph_throughput/main/gatts_sens.h diff --git a/examples/bluetooth/nimble/throughput_app/bleprph_throughput/main/main.c b/examples/bluetooth/nimble/throughput_app/gatt/bleprph_throughput/main/main.c similarity index 100% rename from examples/bluetooth/nimble/throughput_app/bleprph_throughput/main/main.c rename to examples/bluetooth/nimble/throughput_app/gatt/bleprph_throughput/main/main.c diff --git a/examples/bluetooth/nimble/throughput_app/bleprph_throughput/sdkconfig.defaults b/examples/bluetooth/nimble/throughput_app/gatt/bleprph_throughput/sdkconfig.defaults similarity index 90% rename from examples/bluetooth/nimble/throughput_app/bleprph_throughput/sdkconfig.defaults rename to examples/bluetooth/nimble/throughput_app/gatt/bleprph_throughput/sdkconfig.defaults index dcd2fab65be..2b58c85d2ff 100644 --- a/examples/bluetooth/nimble/throughput_app/bleprph_throughput/sdkconfig.defaults +++ b/examples/bluetooth/nimble/throughput_app/gatt/bleprph_throughput/sdkconfig.defaults @@ -5,9 +5,6 @@ # BT config (universal across all ESP32 variants) # CONFIG_BT_ENABLED=y -CONFIG_BTDM_CTRL_MODE_BLE_ONLY=y -CONFIG_BTDM_CTRL_MODE_BR_EDR_ONLY=n -CONFIG_BTDM_CTRL_MODE_BTDM=n CONFIG_BT_BLUEDROID_ENABLED=n CONFIG_BT_NIMBLE_ENABLED=y diff --git a/examples/bluetooth/nimble/throughput_app/bleprph_throughput/sdkconfig.defaults.esp32c6 b/examples/bluetooth/nimble/throughput_app/gatt/bleprph_throughput/sdkconfig.defaults.esp32c6 similarity index 100% rename from examples/bluetooth/nimble/throughput_app/bleprph_throughput/sdkconfig.defaults.esp32c6 rename to examples/bluetooth/nimble/throughput_app/gatt/bleprph_throughput/sdkconfig.defaults.esp32c6 diff --git a/examples/bluetooth/nimble/throughput_app/l2cap_coc/l2cap_coc_cent/CMakeLists.txt b/examples/bluetooth/nimble/throughput_app/l2cap_coc/l2cap_coc_cent/CMakeLists.txt new file mode 100644 index 00000000000..c66bff776f7 --- /dev/null +++ b/examples/bluetooth/nimble/throughput_app/l2cap_coc/l2cap_coc_cent/CMakeLists.txt @@ -0,0 +1,5 @@ +cmake_minimum_required(VERSION 3.22) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +idf_build_set_property(MINIMAL_BUILD ON) +project(l2cap_coc_cent) diff --git a/examples/bluetooth/nimble/throughput_app/l2cap_coc/l2cap_coc_cent/README.md b/examples/bluetooth/nimble/throughput_app/l2cap_coc/l2cap_coc_cent/README.md new file mode 100644 index 00000000000..59b1e64d1fa --- /dev/null +++ b/examples/bluetooth/nimble/throughput_app/l2cap_coc/l2cap_coc_cent/README.md @@ -0,0 +1,94 @@ +| Supported Targets | ESP32 | ESP32-C2 | ESP32-C3 | ESP32-C5 | ESP32-C6 | ESP32-C61 | ESP32-H2 | ESP32-S3 | +| ----------------- | ----- | -------- | -------- | -------- | -------- | --------- | -------- | -------- | + +# L2CAP COC Throughput Central Example + +`l2cap_coc_cent` demonstrates the central (initiator) side of an L2CAP Connection-Oriented Channel (COC) throughput test using NimBLE on ESP32. It passively scans for a peripheral advertising UUID 0x1812, establishes a GAP connection, enables Data Length Extension (DLE), then opens an L2CAP COC channel over PSM 0x1002 and continuously sends SDUs to measure TX throughput. + +The central automatically cycles through all enabled PHYs (1M, 2M, Coded S2, Coded S8) in sequence, printing a throughput summary box after each test interval. It must be used together with the `l2cap_coc_prph` example which acts as the receiving side. + +It uses ESP32's Bluetooth controller and NimBLE stack based BLE host. + +## How to Use Example + +Before project configuration and build, be sure to set the correct chip target using: + +```bash +idf.py set-target +``` + +### Hardware Required + +* Two development boards, one flashed with `l2cap_coc_cent` and the other with `l2cap_coc_prph`. +* A USB cable for power supply and programming. + +See [Development Boards](https://www.espressif.com/en/products/devkits) for more information. + +### Configure the Project + +Open the project configuration menu: + +```bash +idf.py menuconfig +``` + +In the `L2CAP COC Throughput Configuration` menu: + +| Option | Default | Description | +|--------|---------|-------------| +| `EXAMPLE_L2CAP_COC_MTU` | 2048 | L2CAP CoC SDU MTU size in bytes (central receive buffer). Data flows cent → prph in this test, so throughput is governed by the peripheral's MTU. This value only limits how much the peripheral can send back and does not affect TX throughput. | +| `EXAMPLE_EXTENDED_ADV` | y (BLE 5.0 chips) | Enable extended scanning to find peripherals using extended advertising. Required for Coded PHY testing on ESP32-C6/H2. | +| `EXAMPLE_TEST_PHY_1M` | n | Enable throughput test on 1M PHY. | +| `EXAMPLE_TEST_PHY_2M` | y | Enable throughput test on 2M PHY (BLE 5.0 chips only). | +| `EXAMPLE_TEST_PHY_CODED_S2` | n | Enable throughput test on Coded PHY S2 (500 kbps, BLE 5.0 chips only). | +| `EXAMPLE_TEST_PHY_CODED_S8` | n | Enable throughput test on Coded PHY S8 (125 kbps, BLE 5.0 chips only). | +| `EXAMPLE_TEST_DURATION_1M` | 8 | Test duration in seconds for 1M PHY. | +| `EXAMPLE_TEST_DURATION_2M` | 8 | Test duration in seconds for 2M PHY. | +| `EXAMPLE_TEST_DURATION_CODED_S2` | 8 | Test duration in seconds for Coded S2 PHY. | +| `EXAMPLE_TEST_DURATION_CODED_S8` | 8 | Test duration in seconds for Coded S8 PHY. | + +### Build and Flash + +Run `idf.py -p PORT flash monitor` to build, flash and monitor the project. + +(To exit the serial monitor, type ``Ctrl-]``.) + +See the [Getting Started Guide](https://idf.espressif.com/) for full steps to configure and use ESP-IDF to build projects. + +## Example Output + +On successful connection and throughput test, the central prints a per-PHY summary box after each test interval, then loops back to the first enabled PHY continuously: + +``` +I (xxx) l2cap_coc_cent: BLE Host Task started +I (xxx) l2cap_coc_cent: Device Address: xx:xx:xx:xx:xx:xx +I (xxx) l2cap_coc_cent: Connecting to xx:xx:xx:xx:xx:xx (addr_type=0) +I (xxx) l2cap_coc_cent: Connected; handle=0 peer=xx:xx:xx:xx:xx:xx +I (xxx) l2cap_coc_cent: L2CAP COC connected, chan=0xxxxxxxxx +I (xxx) l2cap_coc_cent: L2CAP COC Throughput — TX side (central sends to peripheral) +I (xxx) l2cap_coc_cent: Number of enabled PHYs: x +I (xxx) l2cap_coc_cent: PHY updated: tx=2 rx=2 status=0 +I (xxx) l2cap_coc_cent: [2M PHY] Sending for 8 s +I (xxx) l2cap_coc_cent: +-------------------------------------------------+ +I (xxx) l2cap_coc_cent: | PHY : 2M | +I (xxx) l2cap_coc_cent: | TX : xxxx kbps | +I (xxx) l2cap_coc_cent: | Bytes : xxxxxxx | +I (xxx) l2cap_coc_cent: | Time : 8 s | +I (xxx) l2cap_coc_cent: +-------------------------------------------------+ +I (xxx) l2cap_coc_cent: Cycle complete. Looping back to first PHY... +I (xxx) l2cap_coc_cent: PHY updated: tx=2 rx=2 status=0 +I (xxx) l2cap_coc_cent: [2M PHY] Sending for 8 s +I (xxx) l2cap_coc_cent: +-------------------------------------------------+ +I (xxx) l2cap_coc_cent: | PHY : 2M | +I (xxx) l2cap_coc_cent: | TX : xxxx kbps | +I (xxx) l2cap_coc_cent: | Bytes : xxxxxxx | +I (xxx) l2cap_coc_cent: | Time : 8 s | +I (xxx) l2cap_coc_cent: +-------------------------------------------------+ +I (xxx) l2cap_coc_cent: Cycle complete. Looping back to first PHY... +``` + +> **Note:** The above output was captured on ESP32-H2 with only 2M PHY enabled. With additional PHYs enabled (1M, Coded S2, Coded S8), the central cycles through each in sequence before looping back. + +## Troubleshooting + +For any technical queries, please open an [issue](https://github.com/espressif/esp-idf/issues) on GitHub. We will get back to you soon. diff --git a/examples/bluetooth/nimble/throughput_app/l2cap_coc/l2cap_coc_cent/main/CMakeLists.txt b/examples/bluetooth/nimble/throughput_app/l2cap_coc/l2cap_coc_cent/main/CMakeLists.txt new file mode 100644 index 00000000000..718f89cab8e --- /dev/null +++ b/examples/bluetooth/nimble/throughput_app/l2cap_coc/l2cap_coc_cent/main/CMakeLists.txt @@ -0,0 +1,3 @@ +idf_component_register(SRCS "main.c" + PRIV_REQUIRES bt nvs_flash esp_timer + INCLUDE_DIRS ".") diff --git a/examples/bluetooth/nimble/throughput_app/l2cap_coc/l2cap_coc_cent/main/Kconfig.projbuild b/examples/bluetooth/nimble/throughput_app/l2cap_coc/l2cap_coc_cent/main/Kconfig.projbuild new file mode 100644 index 00000000000..5632e2c99d0 --- /dev/null +++ b/examples/bluetooth/nimble/throughput_app/l2cap_coc/l2cap_coc_cent/main/Kconfig.projbuild @@ -0,0 +1,67 @@ +menu "L2CAP COC Throughput Configuration" + + config EXAMPLE_L2CAP_COC_MTU + int "L2CAP CoC MTU size in bytes" + default 2048 + range 512 65511 + help + L2CAP CoC SDU MTU size used by the central device. + Note: memory pool allocates 6 buffers of this size; total pool + memory = MTU x 6. On chips without PSRAM ensure sufficient heap + is available before increasing this value. + Use idf.py size-components to verify. + + config EXAMPLE_EXTENDED_ADV + bool + depends on SOC_BLE_50_SUPPORTED && BT_NIMBLE_50_FEATURE_SUPPORT + default y if SOC_ESP_NIMBLE_CONTROLLER + select BT_NIMBLE_EXT_ADV + prompt "Enable Extended Scanning" + help + Use extended scanning on chips that support BLE 5.0 + + config EXAMPLE_TEST_PHY_1M + bool "Test on 1M PHY" + default y if !SOC_BLE_50_SUPPORTED + default n + + config EXAMPLE_TEST_PHY_2M + bool "Test on 2M PHY" + default y + depends on SOC_BLE_50_SUPPORTED + + config EXAMPLE_TEST_PHY_CODED_S2 + bool "Test on Coded PHY S2" + default n + depends on SOC_BLE_50_SUPPORTED + + config EXAMPLE_TEST_PHY_CODED_S8 + bool "Test on Coded PHY S8" + default n + depends on SOC_BLE_50_SUPPORTED + + config EXAMPLE_TEST_DURATION_1M + int "Test duration for 1M PHY (sec)" + default 8 + range 1 3600 + depends on EXAMPLE_TEST_PHY_1M + + config EXAMPLE_TEST_DURATION_2M + int "Test duration for 2M PHY (sec)" + default 8 + range 1 3600 + depends on EXAMPLE_TEST_PHY_2M + + config EXAMPLE_TEST_DURATION_CODED_S2 + int "Test duration for Coded S2 PHY (sec)" + default 8 + range 1 3600 + depends on EXAMPLE_TEST_PHY_CODED_S2 + + config EXAMPLE_TEST_DURATION_CODED_S8 + int "Test duration for Coded S8 PHY (sec)" + default 8 + range 1 3600 + depends on EXAMPLE_TEST_PHY_CODED_S8 + +endmenu diff --git a/examples/bluetooth/nimble/throughput_app/l2cap_coc/l2cap_coc_cent/main/idf_component.yml b/examples/bluetooth/nimble/throughput_app/l2cap_coc/l2cap_coc_cent/main/idf_component.yml new file mode 100644 index 00000000000..db8886afea4 --- /dev/null +++ b/examples/bluetooth/nimble/throughput_app/l2cap_coc/l2cap_coc_cent/main/idf_component.yml @@ -0,0 +1,3 @@ +dependencies: + nimble_central_utils: + path: ${IDF_PATH}/examples/bluetooth/nimble/common/nimble_central_utils diff --git a/examples/bluetooth/nimble/throughput_app/l2cap_coc/l2cap_coc_cent/main/main.c b/examples/bluetooth/nimble/throughput_app/l2cap_coc/l2cap_coc_cent/main/main.c new file mode 100644 index 00000000000..09f2c989884 --- /dev/null +++ b/examples/bluetooth/nimble/throughput_app/l2cap_coc/l2cap_coc_cent/main/main.c @@ -0,0 +1,738 @@ +/* + * SPDX-FileCopyrightText: 2025-2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include "esp_log.h" +#include "nvs_flash.h" +#include "esp_timer.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/event_groups.h" +#include "nimble/nimble_port.h" +#include "nimble/nimble_port_freertos.h" +#include "host/ble_hs.h" +#include "host/util/util.h" +#include "services/gap/ble_svc_gap.h" +#include "host/ble_esp_gap.h" + +static const char *TAG = "l2cap_coc_cent"; + +#define L2CAP_COC_PSM 0x1002 +#define L2CAP_COC_MTU CONFIG_EXAMPLE_L2CAP_COC_MTU +#define COC_BUF_COUNT (6 * MYNEWT_VAL(BLE_L2CAP_COC_MAX_NUM)) +/* Block size must include mbuf headers so each SDU fits in one pool entry. */ +#define SDU_BLOCK_SIZE (L2CAP_COC_MTU + sizeof(struct os_mbuf_pkthdr) + sizeof(struct os_mbuf)) +#define LL_PACKET_LENGTH 251 +#define LL_PACKET_TIME 2120 +#define L2CAP_COC_UUID 0x1812 + +/* EventGroup bits */ +#define PHY_UPDATED_BIT (1 << 0) +#define COC_CONNECTED_BIT (1 << 1) +#define CONN_UPDATED_BIT (1 << 2) +#define TX_UNSTALLED_BIT (1 << 3) + +/* Timeout / interval constants */ +#define CONN_PARAM_UPDATE_TIMEOUT_MS 5000 +#define PREDRAIN_TIMEOUT_MS 20000 +#define POSTDRAIN_TIMEOUT_MS 5000 +#define TX_YIELD_INTERVAL 50 + +static EventGroupHandle_t coc_event_group; +static uint16_t conn_handle = BLE_HS_CONN_HANDLE_NONE; +static struct ble_l2cap_chan *coc_chan = NULL; +static bool ci_is_slow = false; +static bool l2cap_connecting = false; /* guards double L2CAP connect */ +static volatile bool chan_stalled = false; +static uint32_t *cent_seg_tx_done = NULL; /* points to segment SDU counter for async TX_UNSTALLED */ +static uint32_t *cent_seg_tx_drop = NULL; /* counts SDUs dropped (TX_UNSTALLED status != 0) */ +static uint16_t cent_tx_sdu_len = L2CAP_COC_MTU; /* min(local, peer) after COC connect */ + +static const struct ble_gap_upd_params conn_params = { + .itvl_min = 6, + .itvl_max = 6, + .latency = 0, + .supervision_timeout = 2000, + .min_ce_len = 12, + .max_ce_len = 24, +}; + +void ble_store_config_init(void); + +static os_membuf_t sdu_coc_mem[OS_MEMPOOL_SIZE(COC_BUF_COUNT, SDU_BLOCK_SIZE)]; +static struct os_mempool sdu_coc_mempool; +static struct os_mbuf_pool sdu_os_mbuf_pool; + +typedef struct { + uint8_t tx_phys; + uint8_t rx_phys; + uint8_t phy_opts; /* 0=none, 1=S2, 2=S8 */ + int duration_s; + const char *name; + bool is_coded_s8; +} phy_entry_t; + +static const phy_entry_t phy_list[] = { +#if CONFIG_EXAMPLE_TEST_PHY_1M + { BLE_HCI_LE_PHY_1M_PREF_MASK, BLE_HCI_LE_PHY_1M_PREF_MASK, 0, + CONFIG_EXAMPLE_TEST_DURATION_1M, "1M", false }, +#endif +#if CONFIG_EXAMPLE_TEST_PHY_2M + { BLE_HCI_LE_PHY_2M_PREF_MASK, BLE_HCI_LE_PHY_2M_PREF_MASK, 0, + CONFIG_EXAMPLE_TEST_DURATION_2M, "2M", false }, +#endif +#if CONFIG_EXAMPLE_TEST_PHY_CODED_S2 + { BLE_HCI_LE_PHY_CODED_PREF_MASK, BLE_HCI_LE_PHY_CODED_PREF_MASK, 0x01, + CONFIG_EXAMPLE_TEST_DURATION_CODED_S2, "Coded S2", false }, +#endif +#if CONFIG_EXAMPLE_TEST_PHY_CODED_S8 + { BLE_HCI_LE_PHY_CODED_PREF_MASK, BLE_HCI_LE_PHY_CODED_PREF_MASK, 0x02, + CONFIG_EXAMPLE_TEST_DURATION_CODED_S8, "Coded S8", true }, +#endif +}; +#define PHY_LIST_LEN ((int)(sizeof(phy_list) / sizeof(phy_list[0]))) + +static int cent_gap_event(struct ble_gap_event *event, void *arg); +static int cent_l2cap_coc_event_cb(struct ble_l2cap_event *event, void *arg); + +static void cent_l2cap_coc_mem_init(void) +{ + int rc; + rc = os_mempool_init(&sdu_coc_mempool, COC_BUF_COUNT, SDU_BLOCK_SIZE, + sdu_coc_mem, "cent_coc_pool"); + assert(rc == 0); + rc = os_mbuf_pool_init(&sdu_os_mbuf_pool, &sdu_coc_mempool, + SDU_BLOCK_SIZE, COC_BUF_COUNT); + assert(rc == 0); +} + +static void cent_l2cap_coc_connect(uint16_t conn_handle) +{ + struct ble_gap_conn_desc desc; + if (ble_gap_conn_find(conn_handle, &desc) != 0) { + ESP_LOGE(TAG, "L2CAP COC connect: connection %d not found", conn_handle); + l2cap_connecting = false; + return; + } + struct os_mbuf *sdu_rx = os_mbuf_get_pkthdr(&sdu_os_mbuf_pool, 0); + if (!sdu_rx) { + ESP_LOGE(TAG, "Failed to alloc sdu_rx for L2CAP connect"); + l2cap_connecting = false; + return; + } + int rc = ble_l2cap_connect(conn_handle, L2CAP_COC_PSM, L2CAP_COC_MTU, + sdu_rx, cent_l2cap_coc_event_cb, NULL); + if (rc != 0) { + ESP_LOGE(TAG, "L2CAP COC connect failed; rc=%d", rc); + l2cap_connecting = false; + /* EINVAL: NimBLE returns before chan alloc, sdu_rx not consumed — free it. + * ENOTCONN: NimBLE frees sdu_rx on all ENOTCONN paths (early !conn check + * and late TX failure via ble_l2cap_coc_cleanup_chan). Do not free here. */ + if (rc == BLE_HS_EINVAL) { + os_mbuf_free_chain(sdu_rx); + } + } +} + +static void cent_scan(void) +{ + struct ble_gap_disc_params disc_params = { + .filter_duplicates = 1, + .passive = 1, + }; + uint8_t own_addr_type; + int rc = ble_hs_id_infer_auto(0, &own_addr_type); + if (rc != 0) { + ESP_LOGE(TAG, "Error inferring addr type; rc=%d", rc); + return; + } + rc = ble_gap_disc(own_addr_type, BLE_HS_FOREVER, &disc_params, + cent_gap_event, NULL); + if (rc != 0) { + ESP_LOGE(TAG, "Error starting scan; rc=%d", rc); + } +} + +static int cent_should_connect(const struct ble_gap_disc_desc *disc) +{ + struct ble_hs_adv_fields fields; + if (disc->event_type != BLE_HCI_ADV_RPT_EVTYPE_ADV_IND && + disc->event_type != BLE_HCI_ADV_RPT_EVTYPE_DIR_IND) { + return 0; + } + int rc = ble_hs_adv_parse_fields(&fields, disc->data, disc->length_data); + if (rc != 0) { + return 0; + } + for (int i = 0; i < fields.num_uuids16; i++) { + if (ble_uuid_u16(&fields.uuids16[i].u) == L2CAP_COC_UUID) { + return 1; + } + } + return 0; +} + +static void cent_connect_if_interesting(const struct ble_gap_disc_desc *disc) +{ + if (!cent_should_connect(disc)) { + return; + } + int rc = ble_gap_disc_cancel(); + if (rc != 0) { + return; + } + uint8_t own_addr_type; + rc = ble_hs_id_infer_auto(0, &own_addr_type); + if (rc != 0) { + ESP_LOGE(TAG, "Error inferring addr type; rc=%d", rc); + cent_scan(); + return; + } + ESP_LOGI(TAG, "Connecting to %02x:%02x:%02x:%02x:%02x:%02x (addr_type=%d)", + disc->addr.val[5], disc->addr.val[4], disc->addr.val[3], + disc->addr.val[2], disc->addr.val[1], disc->addr.val[0], + disc->addr.type); + rc = ble_gap_connect(own_addr_type, &disc->addr, 30000, NULL, + cent_gap_event, NULL); + if (rc != 0) { + ESP_LOGE(TAG, "Connect failed; rc=%d", rc); + cent_scan(); + } +} + +#if CONFIG_EXAMPLE_EXTENDED_ADV +static void cent_connect_if_interesting_ext(const struct ble_gap_ext_disc_desc *disc) +{ + if (!(disc->props & BLE_HCI_ADV_CONN_MASK)) { + return; + } + struct ble_hs_adv_fields fields; + if (ble_hs_adv_parse_fields(&fields, disc->data, disc->length_data) != 0) { + return; + } + int found = 0; + for (int i = 0; i < fields.num_uuids16; i++) { + if (ble_uuid_u16(&fields.uuids16[i].u) == L2CAP_COC_UUID) { + found = 1; + break; + } + } + if (!found) { + return; + } + int rc = ble_gap_disc_cancel(); + if (rc != 0) { + return; + } + uint8_t own_addr_type; + rc = ble_hs_id_infer_auto(0, &own_addr_type); + if (rc != 0) { + ESP_LOGE(TAG, "Error inferring addr type; rc=%d", rc); + cent_scan(); + return; + } + ESP_LOGI(TAG, "Connecting to %02x:%02x:%02x:%02x:%02x:%02x (addr_type=%d)", + disc->addr.val[5], disc->addr.val[4], disc->addr.val[3], + disc->addr.val[2], disc->addr.val[1], disc->addr.val[0], + disc->addr.type); + rc = ble_gap_connect(own_addr_type, &disc->addr, 30000, NULL, + cent_gap_event, NULL); + if (rc != 0) { + ESP_LOGE(TAG, "Connect failed; rc=%d", rc); + cent_scan(); + } +} +#endif /* CONFIG_EXAMPLE_EXTENDED_ADV */ + +static int cent_l2cap_coc_event_cb(struct ble_l2cap_event *event, void *arg) +{ + switch (event->type) { + case BLE_L2CAP_EVENT_COC_CONNECTED: { + struct ble_l2cap_chan_info info; + uint16_t peer_mtu; + + if (event->connect.status != 0) { + ESP_LOGE(TAG, "L2CAP COC connect status: %d,terminating GAP to restart", event->connect.status); + l2cap_connecting = false; + ble_gap_terminate(conn_handle, BLE_ERR_REM_USER_CONN_TERM); + return 0; + } + ESP_LOGI(TAG, "L2CAP COC connected, chan=%p", event->connect.chan); + coc_chan = event->connect.chan; + + peer_mtu = 0; + cent_tx_sdu_len = L2CAP_COC_MTU; + if (ble_l2cap_get_chan_info(coc_chan, &info) == 0) { + peer_mtu = info.peer_coc_mtu; + if (peer_mtu > 0 && peer_mtu < cent_tx_sdu_len) { + cent_tx_sdu_len = peer_mtu; + } + } + ESP_LOGI(TAG, "TX SDU size: %u bytes (peer CoC MTU %u)", + cent_tx_sdu_len, peer_mtu); + l2cap_connecting = false; + xEventGroupSetBits(coc_event_group, COC_CONNECTED_BIT); + return 0; + } + + case BLE_L2CAP_EVENT_COC_DISCONNECTED: + ESP_LOGI(TAG, "L2CAP COC disconnected"); + coc_chan = NULL; + chan_stalled = false; + cent_seg_tx_done = NULL; + cent_seg_tx_drop = NULL; + l2cap_connecting = false; + xEventGroupClearBits(coc_event_group, COC_CONNECTED_BIT); + xEventGroupSetBits(coc_event_group, TX_UNSTALLED_BIT | CONN_UPDATED_BIT | PHY_UPDATED_BIT); + return 0; + + case BLE_L2CAP_EVENT_COC_TX_UNSTALLED: + /* status==0: SDU delivered; status!=0: NimBLE dropped it (ENOMEM) — don't count. */ + chan_stalled = false; + if (event->tx_unstalled.status == 0) { + if (cent_seg_tx_done) { + (*cent_seg_tx_done)++; + } + } else { + if (cent_seg_tx_drop) { + (*cent_seg_tx_drop)++; + } + ESP_LOGD(TAG, "TX_UNSTALLED status=%d: SDU dropped", event->tx_unstalled.status); + } + xEventGroupSetBits(coc_event_group, TX_UNSTALLED_BIT); + return 0; + + case BLE_L2CAP_EVENT_COC_DATA_RECEIVED: { + struct os_mbuf *sdu_rx; + int rc; + + if (event->receive.sdu_rx) { + os_mbuf_free_chain(event->receive.sdu_rx); + } + sdu_rx = os_mbuf_get_pkthdr(&sdu_os_mbuf_pool, 0); + if (sdu_rx) { + rc = ble_l2cap_recv_ready(event->receive.chan, sdu_rx); + if (rc != 0) { + os_mbuf_free_chain(sdu_rx); + } + } else { + ESP_LOGE(TAG, "DATA_RECEIVED: no mbuf for recv_ready; RX may stall"); + } + return 0; + } + + default: + return 0; + } +} + +static void wait_unstall(uint32_t timeout_ms) +{ + xEventGroupWaitBits(coc_event_group, TX_UNSTALLED_BIT, pdTRUE, pdTRUE, pdMS_TO_TICKS(timeout_ms)); +} + +static void cent_send_task(void *arg) +{ + static uint8_t value[L2CAP_COC_MTU]; + int rc; + + for (int i = 0; i < L2CAP_COC_MTU; i++) { + value[i] = i & 0xFF; + } + + xEventGroupWaitBits(coc_event_group, COC_CONNECTED_BIT, pdFALSE, pdTRUE, portMAX_DELAY); + + ESP_LOGI(TAG, "L2CAP COC Throughput — TX side (central sends to peripheral)"); + ESP_LOGI(TAG, "Number of enabled PHYs: %d", (int)PHY_LIST_LEN); + + if (PHY_LIST_LEN == 0) { + ESP_LOGE(TAG, "No test PHY enabled; enable at least one EXAMPLE_TEST_PHY_* in menuconfig"); + vTaskDelete(NULL); + return; + } + + while (1) { + bool lost_connection = false; + + for (int i = 0; i < PHY_LIST_LEN && !lost_connection; i++) { + const phy_entry_t *phy = &phy_list[i]; + + xEventGroupClearBits(coc_event_group, PHY_UPDATED_BIT); +#if CONFIG_SOC_BLE_50_SUPPORTED + rc = ble_gap_set_prefered_le_phy(conn_handle, + phy->tx_phys, + phy->rx_phys, + phy->phy_opts); + if (rc != 0) { + ESP_LOGE(TAG, "PHY switch to %s failed; rc=%d — continuing anyway", + phy->name, rc); + xEventGroupSetBits(coc_event_group, PHY_UPDATED_BIT); + } +#else + xEventGroupSetBits(coc_event_group, PHY_UPDATED_BIT); +#endif + + EventBits_t bits = xEventGroupWaitBits(coc_event_group, PHY_UPDATED_BIT, + pdTRUE, pdTRUE, + pdMS_TO_TICKS(5000)); + if (!(bits & PHY_UPDATED_BIT)) { + ESP_LOGW(TAG, "PHY update timeout for %s; continuing anyway", phy->name); + } + + if (phy->is_coded_s8) { + /* CI=40ms, CE=32.5-40ms: fits 2 Coded S8 K-frames per CI and prevents credit starvation */ + struct ble_gap_upd_params s8_params = { + .itvl_min = 32, + .itvl_max = 32, + .latency = 0, + .supervision_timeout = 2000, + .min_ce_len = 52, + .max_ce_len = 64, + }; + xEventGroupClearBits(coc_event_group, CONN_UPDATED_BIT); + rc = ble_gap_update_params(conn_handle, &s8_params); + if (rc == 0) { + ESP_LOGI(TAG, "Coded S8: updating CI"); + ci_is_slow = true; + xEventGroupWaitBits(coc_event_group, CONN_UPDATED_BIT, pdTRUE, pdTRUE, + pdMS_TO_TICKS(CONN_PARAM_UPDATE_TIMEOUT_MS)); + } else { + ESP_LOGW(TAG, "S8 CI update failed (rc=%d)", rc); + } + } else if (phy->tx_phys == BLE_HCI_LE_PHY_CODED_PREF_MASK) { + /* CI=20ms, CE=10-20ms: fits 2 Coded S2 K-frames per CI */ + struct ble_gap_upd_params s2_params = { + .itvl_min = 16, + .itvl_max = 16, + .latency = 0, + .supervision_timeout = 2000, + .min_ce_len = 16, + .max_ce_len = 32, + }; + xEventGroupClearBits(coc_event_group, CONN_UPDATED_BIT); + rc = ble_gap_update_params(conn_handle, &s2_params); + if (rc == 0) { + ESP_LOGI(TAG, "Coded S2: updating CI"); + ci_is_slow = true; + xEventGroupWaitBits(coc_event_group, CONN_UPDATED_BIT, pdTRUE, pdTRUE, + pdMS_TO_TICKS(CONN_PARAM_UPDATE_TIMEOUT_MS)); + } else { + ESP_LOGW(TAG, "S2 CI update failed (rc=%d)", rc); + } + } else if (ci_is_slow) { + ci_is_slow = false; + xEventGroupClearBits(coc_event_group, CONN_UPDATED_BIT); + rc = ble_gap_update_params(conn_handle, &conn_params); + if (rc == 0) { + ESP_LOGI(TAG, "%s: restoring CI to 6 ms", phy->name); + xEventGroupWaitBits(coc_event_group, CONN_UPDATED_BIT, pdTRUE, pdTRUE, + pdMS_TO_TICKS(CONN_PARAM_UPDATE_TIMEOUT_MS)); + } else { + ESP_LOGW(TAG, "CI restore failed (rc=%d)", rc); + } + } + + if (chan_stalled) { + ESP_LOGI(TAG, "Pre-drain: waiting for unstall"); + wait_unstall(PREDRAIN_TIMEOUT_MS); + if (chan_stalled) { + ESP_LOGW(TAG, "Pre-drain timed out; forcing clear"); + chan_stalled = false; + } + } + + int64_t start_us = esp_timer_get_time(); + int64_t end_us = start_us + (int64_t)phy->duration_s * 1000000LL; + /* Both the send task and TX_UNSTALLED callback update these counters; a + * lost update is possible on dual-core but harmless for throughput stats. */ + + uint32_t segment_sdus = 0; + uint32_t segment_drops = 0; + + cent_seg_tx_done = &segment_sdus; + cent_seg_tx_drop = &segment_drops; + + xEventGroupClearBits(coc_event_group, TX_UNSTALLED_BIT); /* clear stale signal from previous segment */ + + ESP_LOGI(TAG, "[%s PHY] Sending for %d s", phy->name, phy->duration_s); + + while (!lost_connection && esp_timer_get_time() < end_us) { + /* Snapshot coc_chan — NimBLE host task can NULL it between check and send. */ + struct ble_l2cap_chan *chan = coc_chan; + if (!chan) { + ESP_LOGW(TAG, "COC channel lost during test"); + cent_seg_tx_done = NULL; + cent_seg_tx_drop = NULL; + lost_connection = true; + break; + } + + struct os_mbuf *sdu_tx = os_mbuf_get_pkthdr(&sdu_os_mbuf_pool, 0); + if (!sdu_tx) { + vTaskDelay(1); + continue; + } + + rc = os_mbuf_append(sdu_tx, value, cent_tx_sdu_len); + if (rc != 0) { + os_mbuf_free_chain(sdu_tx); + continue; + } + + rc = ble_l2cap_send(chan, sdu_tx); + + if (rc == 0) { + segment_sdus++; + } else if (rc == BLE_HS_ESTALLED) { + chan_stalled = true; + xEventGroupWaitBits(coc_event_group, TX_UNSTALLED_BIT, pdTRUE, pdTRUE, pdMS_TO_TICKS(100)); + continue; + } else if (rc == BLE_HS_EBUSY) { + os_mbuf_free_chain(sdu_tx); + if (chan_stalled) { + xEventGroupWaitBits(coc_event_group, TX_UNSTALLED_BIT, pdTRUE, pdTRUE, pdMS_TO_TICKS(100)); + } else { + taskYIELD(); + } + continue; + } else if (rc == BLE_HS_ENOMEM) { + vTaskDelay(1); + continue; + } else { + ESP_LOGE(TAG, "Send failed; rc=%d", rc); + if (rc == BLE_HS_EBADDATA) { + os_mbuf_free_chain(sdu_tx); + } + break; + } + + if (segment_sdus % TX_YIELD_INTERVAL == 0 && segment_sdus > 0) { + vTaskDelay(1); + } + } + + if (!coc_chan && !lost_connection) { + cent_seg_tx_done = NULL; + cent_seg_tx_drop = NULL; + lost_connection = true; + } + + if (lost_connection) { + break; + } + + if (chan_stalled) { + ESP_LOGI(TAG, "Post-drain: waiting for unstall"); + wait_unstall(POSTDRAIN_TIMEOUT_MS); + if (chan_stalled) { + ESP_LOGW(TAG, "Post-drain timed out; pre-drain will retry"); + } + } + + int64_t elapsed_us = esp_timer_get_time() - start_us; + if (elapsed_us < 1) { elapsed_us = 1; } + + uint64_t bytes_sent = (uint64_t)segment_sdus * cent_tx_sdu_len; + uint32_t elapsed_ms = (uint32_t)(elapsed_us / 1000); + if (elapsed_ms == 0) { elapsed_ms = 1; } + uint32_t tp_kbps = (uint32_t)((bytes_sent * 8ULL) / elapsed_ms); + uint32_t dropped = segment_drops; + + cent_seg_tx_done = NULL; + cent_seg_tx_drop = NULL; + + ESP_LOGI(TAG, "+-------------------------------------------------+"); + ESP_LOGI(TAG, "| PHY : %-39s|", phy->name); + ESP_LOGI(TAG, "| TX : %-6" PRIu32 " kbps |", tp_kbps); + ESP_LOGI(TAG, "| Bytes : %-10" PRIu64 " |", bytes_sent); + ESP_LOGI(TAG, "| Time : %-5" PRIu32 " s |", elapsed_ms / 1000); + ESP_LOGI(TAG, "+-------------------------------------------------+"); + if (dropped > 0) { + ESP_LOGW(TAG, "%" PRIu32 " SDUs dropped (ENOMEM); raise BT_NIMBLE_MSYS_1_BLOCK_COUNT", + dropped); + } + } + + if (lost_connection) { + ESP_LOGI(TAG, "Waiting for L2CAP COC reconnection..."); + /* disconnect handler already cleared COC_CONNECTED_BIT; clearing it + * again here could cancel a bit set by a reconnect that raced ahead. */ + xEventGroupWaitBits(coc_event_group, COC_CONNECTED_BIT, pdFALSE, pdTRUE, portMAX_DELAY); + } else { + ESP_LOGI(TAG, "Cycle complete. Looping back to first PHY..."); + } + } +} + +static int cent_gap_event(struct ble_gap_event *event, void *arg) +{ + int rc; + + switch (event->type) { + case BLE_GAP_EVENT_DISC: + cent_connect_if_interesting(&event->disc); + return 0; + +#if CONFIG_EXAMPLE_EXTENDED_ADV + case BLE_GAP_EVENT_EXT_DISC: + cent_connect_if_interesting_ext(&event->ext_disc); + return 0; +#endif + + case BLE_GAP_EVENT_CONNECT: + if (event->connect.status == 0) { + struct ble_gap_conn_desc desc; + if (ble_gap_conn_find(event->connect.conn_handle, &desc) == 0) { + ESP_LOGI(TAG, "Connected; handle=%d peer=%02x:%02x:%02x:%02x:%02x:%02x", + event->connect.conn_handle, + desc.peer_id_addr.val[5], desc.peer_id_addr.val[4], + desc.peer_id_addr.val[3], desc.peer_id_addr.val[2], + desc.peer_id_addr.val[1], desc.peer_id_addr.val[0]); + } + + conn_handle = event->connect.conn_handle; + l2cap_connecting = false; + + rc = ble_hs_hci_util_set_data_len(conn_handle, + LL_PACKET_LENGTH, LL_PACKET_TIME); + if (rc != 0) { + /* DATA_LEN_CHG won't fire — connect L2CAP directly as fallback */ + ESP_LOGE(TAG, "Set packet length failed; rc=%d, connecting L2CAP directly", rc); + l2cap_connecting = true; + cent_l2cap_coc_connect(conn_handle); + } else { + /* DLE accepted; connect L2CAP now — DATA_LEN_CHG may not fire if + * the peer's data length is already at the requested value */ + l2cap_connecting = true; + cent_l2cap_coc_connect(conn_handle); + } + } else { + ESP_LOGE(TAG, "Connection failed; status=%d", event->connect.status); + cent_scan(); + } + return 0; + + case BLE_GAP_EVENT_DISCONNECT: + ESP_LOGI(TAG, "Disconnected; reason=%d", event->disconnect.reason); + conn_handle = BLE_HS_CONN_HANDLE_NONE; + coc_chan = NULL; + chan_stalled = false; + ci_is_slow = false; + l2cap_connecting = false; + xEventGroupClearBits(coc_event_group, COC_CONNECTED_BIT); + xEventGroupSetBits(coc_event_group, TX_UNSTALLED_BIT | CONN_UPDATED_BIT | PHY_UPDATED_BIT); + cent_scan(); + return 0; + + case BLE_GAP_EVENT_PHY_UPDATE_COMPLETE: + ESP_LOGI(TAG, "PHY updated: tx=%d rx=%d status=%d", + event->phy_updated.tx_phy, + event->phy_updated.rx_phy, + event->phy_updated.status); + if (event->phy_updated.status != 0) { + ESP_LOGW(TAG, "PHY update failed; status=%d", event->phy_updated.status); + } + xEventGroupSetBits(coc_event_group, PHY_UPDATED_BIT); + return 0; + + case BLE_GAP_EVENT_CONN_UPDATE: + ESP_LOGI(TAG, "Conn params updated; status=%d", event->conn_update.status); + if (event->conn_update.status != 0) { + ESP_LOGW(TAG, "Connection parameter update failed (status=%d)", + event->conn_update.status); + } + xEventGroupSetBits(coc_event_group, CONN_UPDATED_BIT); + return 0; + + case BLE_GAP_EVENT_DATA_LEN_CHG: + /* fires for TX and RX; guard ensures connect called only once */ + if (!l2cap_connecting && coc_chan == NULL) { + l2cap_connecting = true; + cent_l2cap_coc_connect(conn_handle); + } + return 0; + + case BLE_GAP_EVENT_DISC_COMPLETE: + ESP_LOGI(TAG, "Discovery complete; reason=%d", event->disc_complete.reason); + return 0; + + default: + return 0; + } +} + +static void cent_on_reset(int reason) +{ + ESP_LOGE(TAG, "Host reset; reason=%d", reason); +} + +static void cent_on_sync(void) +{ + int rc = ble_hs_util_ensure_addr(0); + assert(rc == 0); + + uint8_t own_addr_type; + uint8_t addr[6] = {0}; + + rc = ble_hs_id_infer_auto(0, &own_addr_type); + if (rc != 0) { + ESP_LOGE(TAG, "Error inferring addr type; rc=%d", rc); + return; + } + ble_hs_id_copy_addr(own_addr_type, addr, NULL); + ESP_LOGI(TAG, "Device Address: %02x:%02x:%02x:%02x:%02x:%02x", + addr[5], addr[4], addr[3], addr[2], addr[1], addr[0]); + + cent_scan(); +} + +static void cent_host_task(void *param) +{ + ESP_LOGI(TAG, "BLE Host Task started"); + nimble_port_run(); + nimble_port_freertos_deinit(); +} + +void app_main(void) +{ + esp_err_t 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); + + coc_event_group = xEventGroupCreate(); + assert(coc_event_group); + + ret = nimble_port_init(); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "nimble_port_init failed; rc=%d", ret); + return; + } + + cent_l2cap_coc_mem_init(); + + ble_hs_cfg.reset_cb = cent_on_reset; + ble_hs_cfg.sync_cb = cent_on_sync; + ble_hs_cfg.store_status_cb = ble_store_util_status_rr; + +#if CONFIG_BT_NIMBLE_GAP_SERVICE + int rc = ble_svc_gap_device_name_set("l2cap-coc-cent"); + assert(rc == 0); +#endif + + ble_store_config_init(); + + if (xTaskCreate(cent_send_task, "cent_send_task", 4096, NULL, 5, NULL) != pdPASS) { + ESP_LOGE(TAG, "Failed to create cent_send_task"); + return; + } + + nimble_port_freertos_init(cent_host_task); +} diff --git a/examples/bluetooth/nimble/throughput_app/l2cap_coc/l2cap_coc_cent/sdkconfig.defaults b/examples/bluetooth/nimble/throughput_app/l2cap_coc/l2cap_coc_cent/sdkconfig.defaults new file mode 100644 index 00000000000..484df7d544b --- /dev/null +++ b/examples/bluetooth/nimble/throughput_app/l2cap_coc/l2cap_coc_cent/sdkconfig.defaults @@ -0,0 +1,16 @@ +CONFIG_BT_ENABLED=y +CONFIG_BT_NIMBLE_ENABLED=y +CONFIG_BT_NIMBLE_ATT_PREFERRED_MTU=512 +CONFIG_BT_NIMBLE_TRANSPORT_EVT_SIZE=255 +CONFIG_BT_NIMBLE_LOG_LEVEL=4 +CONFIG_BT_NIMBLE_LOG_LEVEL_NONE=y +CONFIG_BT_NIMBLE_MSYS_1_BLOCK_COUNT=400 +CONFIG_BT_NIMBLE_MSYS_1_BLOCK_SIZE=255 +CONFIG_BT_NIMBLE_MSYS_2_BLOCK_COUNT=50 +CONFIG_BT_NIMBLE_MSYS_2_BLOCK_SIZE=260 +CONFIG_BT_NIMBLE_L2CAP_COC_MAX_NUM=1 +CONFIG_BT_NIMBLE_L2CAP_COC_SDU_BUFF_COUNT=12 +CONFIG_EXAMPLE_L2CAP_COC_MTU=2048 +CONFIG_BT_NIMBLE_TRANSPORT_ACL_FROM_LL_COUNT=67 +CONFIG_FREERTOS_HZ=1000 +CONFIG_ESP_TASK_WDT_TIMEOUT_S=30 diff --git a/examples/bluetooth/nimble/throughput_app/l2cap_coc/l2cap_coc_cent/sdkconfig.defaults.esp32 b/examples/bluetooth/nimble/throughput_app/l2cap_coc/l2cap_coc_cent/sdkconfig.defaults.esp32 new file mode 100644 index 00000000000..d11249b3f31 --- /dev/null +++ b/examples/bluetooth/nimble/throughput_app/l2cap_coc/l2cap_coc_cent/sdkconfig.defaults.esp32 @@ -0,0 +1,4 @@ +CONFIG_EXAMPLE_L2CAP_COC_MTU=2048 +CONFIG_BT_NIMBLE_MSYS_1_BLOCK_COUNT=96 +CONFIG_BT_NIMBLE_MSYS_2_BLOCK_COUNT=48 +CONFIG_BT_NIMBLE_TRANSPORT_ACL_FROM_LL_COUNT=10 diff --git a/examples/bluetooth/nimble/throughput_app/l2cap_coc/l2cap_coc_cent/sdkconfig.defaults.esp32c2 b/examples/bluetooth/nimble/throughput_app/l2cap_coc/l2cap_coc_cent/sdkconfig.defaults.esp32c2 new file mode 100644 index 00000000000..7afcaef3d97 --- /dev/null +++ b/examples/bluetooth/nimble/throughput_app/l2cap_coc/l2cap_coc_cent/sdkconfig.defaults.esp32c2 @@ -0,0 +1,6 @@ +# ACL_FROM_LL_COUNT kept at 67 (not reduced like MSYS pools) to avoid 0 kbps +# throughput after Coded S2 <-> S8 PHY switches on this RAM-limited target. +CONFIG_BT_NIMBLE_MSYS_1_BLOCK_COUNT=12 +CONFIG_BT_NIMBLE_MSYS_1_BLOCK_SIZE=292 +CONFIG_BT_NIMBLE_MSYS_2_BLOCK_COUNT=0 +CONFIG_BT_NIMBLE_TRANSPORT_ACL_FROM_LL_COUNT=67 diff --git a/examples/bluetooth/nimble/throughput_app/l2cap_coc/l2cap_coc_cent/sdkconfig.defaults.esp32c3 b/examples/bluetooth/nimble/throughput_app/l2cap_coc/l2cap_coc_cent/sdkconfig.defaults.esp32c3 new file mode 100644 index 00000000000..8fd238fb854 --- /dev/null +++ b/examples/bluetooth/nimble/throughput_app/l2cap_coc/l2cap_coc_cent/sdkconfig.defaults.esp32c3 @@ -0,0 +1,4 @@ +CONFIG_BT_NIMBLE_MSYS_1_BLOCK_COUNT=160 +CONFIG_BT_NIMBLE_MSYS_2_BLOCK_COUNT=48 +CONFIG_BT_NIMBLE_TRANSPORT_ACL_FROM_LL_COUNT=24 +CONFIG_BT_CTRL_BLE_STATIC_ACL_TX_BUF_NB=8 diff --git a/examples/bluetooth/nimble/throughput_app/l2cap_coc/l2cap_coc_cent/sdkconfig.defaults.esp32c6 b/examples/bluetooth/nimble/throughput_app/l2cap_coc/l2cap_coc_cent/sdkconfig.defaults.esp32c6 new file mode 100644 index 00000000000..589bf21d601 --- /dev/null +++ b/examples/bluetooth/nimble/throughput_app/l2cap_coc/l2cap_coc_cent/sdkconfig.defaults.esp32c6 @@ -0,0 +1,3 @@ +CONFIG_BT_NIMBLE_MSYS_1_BLOCK_COUNT=120 +CONFIG_BT_NIMBLE_MSYS_2_BLOCK_COUNT=48 +CONFIG_BT_NIMBLE_TRANSPORT_ACL_FROM_LL_COUNT=67 diff --git a/examples/bluetooth/nimble/throughput_app/l2cap_coc/l2cap_coc_cent/sdkconfig.defaults.esp32h2 b/examples/bluetooth/nimble/throughput_app/l2cap_coc/l2cap_coc_cent/sdkconfig.defaults.esp32h2 new file mode 100644 index 00000000000..589bf21d601 --- /dev/null +++ b/examples/bluetooth/nimble/throughput_app/l2cap_coc/l2cap_coc_cent/sdkconfig.defaults.esp32h2 @@ -0,0 +1,3 @@ +CONFIG_BT_NIMBLE_MSYS_1_BLOCK_COUNT=120 +CONFIG_BT_NIMBLE_MSYS_2_BLOCK_COUNT=48 +CONFIG_BT_NIMBLE_TRANSPORT_ACL_FROM_LL_COUNT=67 diff --git a/examples/bluetooth/nimble/throughput_app/l2cap_coc/l2cap_coc_prph/CMakeLists.txt b/examples/bluetooth/nimble/throughput_app/l2cap_coc/l2cap_coc_prph/CMakeLists.txt new file mode 100644 index 00000000000..c4181070e58 --- /dev/null +++ b/examples/bluetooth/nimble/throughput_app/l2cap_coc/l2cap_coc_prph/CMakeLists.txt @@ -0,0 +1,5 @@ +cmake_minimum_required(VERSION 3.22) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +idf_build_set_property(MINIMAL_BUILD ON) +project(l2cap_coc_prph) diff --git a/examples/bluetooth/nimble/throughput_app/l2cap_coc/l2cap_coc_prph/README.md b/examples/bluetooth/nimble/throughput_app/l2cap_coc/l2cap_coc_prph/README.md new file mode 100644 index 00000000000..461d4c07058 --- /dev/null +++ b/examples/bluetooth/nimble/throughput_app/l2cap_coc/l2cap_coc_prph/README.md @@ -0,0 +1,77 @@ +| Supported Targets | ESP32 | ESP32-C2 | ESP32-C3 | ESP32-C5 | ESP32-C6 | ESP32-C61 | ESP32-H2 | ESP32-S3 | +| ----------------- | ----- | -------- | -------- | -------- | -------- | --------- | -------- | -------- | + +# L2CAP COC Throughput Peripheral Example + +`l2cap_coc_prph` demonstrates the peripheral side of an L2CAP Connection-Oriented Channel (COC) throughput test using NimBLE on ESP32. It advertises with UUID 0x1812, accepts an incoming GAP connection from `l2cap_coc_cent`, registers an L2CAP COC server on PSM 0x1002, and measures RX throughput as the central sends SDUs. + +The peripheral tracks throughput per PHY — each time the central switches PHY, the peripheral prints a per-PHY throughput summary box and resets its counters. A background stats task also prints a live per-second RX rate while data is flowing. It must be used together with the `l2cap_coc_cent` example which acts as the sending side. + +It uses ESP32's Bluetooth controller and NimBLE stack based BLE host. + +## How to Use Example + +Before project configuration and build, be sure to set the correct chip target using: + +```bash +idf.py set-target +``` + +### Hardware Required + +* Two development boards, one flashed with `l2cap_coc_prph` and the other with `l2cap_coc_cent`. +* A USB cable for power supply and programming. + +See [Development Boards](https://www.espressif.com/en/products/devkits) for more information. + +### Configure the Project + +Open the project configuration menu: + +```bash +idf.py menuconfig +``` + +In the `L2CAP COC Throughput Configuration` menu: + +| Option | Default | Description | +|---------|---------|-------------| +| `EXAMPLE_L2CAP_COC_MTU` | `16384` | Peripheral L2CAP CoC SDU MTU size in bytes. | +| `EXAMPLE_EXTENDED_ADV` | `y` (BLE 5.0 chips) | Enable extended advertising for BLE 5.0 capable devices. Required for Coded PHY testing on ESP32-C6 and ESP32-H2. | + +> **Note:** Throughput in the central → peripheral direction is primarily determined by the peripheral MTU. With the default configuration (`MTU=16384`, `MPS=247`), NimBLE grants approximately 67 initial credits to the central sender, allowing multiple packets to remain in flight and maximizing link throughput. + +### Build and Flash + +Run `idf.py -p PORT flash monitor` to build, flash and monitor the project. + +(To exit the serial monitor, type ``Ctrl-]``.) + +See the [Getting Started Guide](https://idf.espressif.com/) for full steps to configure and use ESP-IDF to build projects. + +## Example Output + +On successful connection and data reception, the peripheral prints a live per-second RX rate while data flows: + +``` +I (xxx) l2cap_coc_prph: BLE Host Task started +I (xxx) l2cap_coc_prph: Device Address: xx:xx:xx:xx:xx:xx +I (xxx) l2cap_coc_prph: Extended advertising started +I (xxx) l2cap_coc_prph: Connected; handle=0 +I (xxx) l2cap_coc_prph: L2CAP COC connected, chan=0xxxxxxxxx +I (xxx) l2cap_coc_prph: PHY updated: tx=2 rx=2 status=0 +I (xxx) l2cap_coc_prph: | RX : xxxx kbps | +I (xxx) l2cap_coc_prph: | RX : xxxx kbps | +I (xxx) l2cap_coc_prph: | RX : xxxx kbps | +I (xxx) l2cap_coc_prph: | RX : xxxx kbps | +I (xxx) l2cap_coc_prph: | RX : xxxx kbps | +I (xxx) l2cap_coc_prph: | RX : xxxx kbps | +I (xxx) l2cap_coc_prph: | RX : xxxx kbps | +I (xxx) l2cap_coc_prph: | RX : xxxx kbps | +``` + +> **Note:** The peripheral prints one RX line per second. The central controls PHY selection and test duration; the peripheral tracks and displays throughput continuously as long as data is flowing. + +## Troubleshooting + +For any technical queries, please open an [issue](https://github.com/espressif/esp-idf/issues) on GitHub. We will get back to you soon. diff --git a/examples/bluetooth/nimble/throughput_app/l2cap_coc/l2cap_coc_prph/main/CMakeLists.txt b/examples/bluetooth/nimble/throughput_app/l2cap_coc/l2cap_coc_prph/main/CMakeLists.txt new file mode 100644 index 00000000000..718f89cab8e --- /dev/null +++ b/examples/bluetooth/nimble/throughput_app/l2cap_coc/l2cap_coc_prph/main/CMakeLists.txt @@ -0,0 +1,3 @@ +idf_component_register(SRCS "main.c" + PRIV_REQUIRES bt nvs_flash esp_timer + INCLUDE_DIRS ".") diff --git a/examples/bluetooth/nimble/throughput_app/l2cap_coc/l2cap_coc_prph/main/Kconfig.projbuild b/examples/bluetooth/nimble/throughput_app/l2cap_coc/l2cap_coc_prph/main/Kconfig.projbuild new file mode 100644 index 00000000000..ee49e5894fd --- /dev/null +++ b/examples/bluetooth/nimble/throughput_app/l2cap_coc/l2cap_coc_prph/main/Kconfig.projbuild @@ -0,0 +1,23 @@ +menu "L2CAP COC Throughput Configuration" + + config EXAMPLE_L2CAP_COC_MTU + int "L2CAP CoC MTU size in bytes" + default 16384 + range 512 65511 + help + L2CAP CoC SDU MTU size in bytes. + Note: memory pool allocates 6 buffers of this size; total pool + memory = MTU x 6. On chips without PSRAM ensure sufficient heap + is available before increasing this value. + Use idf.py size-components to verify. + + config EXAMPLE_EXTENDED_ADV + bool + depends on SOC_BLE_50_SUPPORTED && BT_NIMBLE_50_FEATURE_SUPPORT + default y if SOC_ESP_NIMBLE_CONTROLLER + select BT_NIMBLE_EXT_ADV + prompt "Enable Extended Advertising" + help + Enable BLE 5.0 extended advertising. + +endmenu diff --git a/examples/bluetooth/nimble/throughput_app/l2cap_coc/l2cap_coc_prph/main/main.c b/examples/bluetooth/nimble/throughput_app/l2cap_coc/l2cap_coc_prph/main/main.c new file mode 100644 index 00000000000..99767c15b84 --- /dev/null +++ b/examples/bluetooth/nimble/throughput_app/l2cap_coc/l2cap_coc_prph/main/main.c @@ -0,0 +1,445 @@ +/* + * SPDX-FileCopyrightText: 2025-2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include "esp_log.h" +#include "nvs_flash.h" +#include "esp_timer.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "nimble/nimble_port.h" +#include "nimble/nimble_port_freertos.h" +#include "host/ble_hs.h" +#include "host/util/util.h" +#include "services/gap/ble_svc_gap.h" + +static const char *TAG = "l2cap_coc_prph"; + +#define L2CAP_COC_PSM 0x1002 +#define L2CAP_COC_MTU CONFIG_EXAMPLE_L2CAP_COC_MTU +#define COC_BUF_COUNT (6 * MYNEWT_VAL(BLE_L2CAP_COC_MAX_NUM)) +/* Block size must include mbuf headers so each SDU fits in one pool entry. */ +#define SDU_BLOCK_SIZE (L2CAP_COC_MTU + sizeof(struct os_mbuf_pkthdr) + sizeof(struct os_mbuf)) +#define LL_PACKET_LENGTH 251 +#define LL_PACKET_TIME 2120 +#define L2CAP_COC_UUID 0x1812 + +static uint16_t conn_handle = BLE_HS_CONN_HANDLE_NONE; +static struct ble_l2cap_chan *coc_chan = NULL; +static uint8_t own_addr_type; + +static int64_t phy_start_time = 0; +static volatile uint32_t rx_bytes = 0; +static uint32_t rx_packets = 0; +static volatile bool coc_active = false; +static const char *phy_name = "1M"; +static uint8_t current_phy = BLE_HCI_LE_PHY_1M; + +void ble_store_config_init(void); + +static os_membuf_t sdu_coc_mem[OS_MEMPOOL_SIZE(COC_BUF_COUNT, SDU_BLOCK_SIZE)]; +static struct os_mempool sdu_coc_mempool; +static struct os_mbuf_pool sdu_os_mbuf_pool; + +static int prph_gap_event(struct ble_gap_event *event, void *arg); + +static const char *prph_phy_str(uint8_t phy) +{ + switch (phy) { + case BLE_HCI_LE_PHY_2M: return "2M"; + case BLE_HCI_LE_PHY_CODED: return "Coded"; + default: return "1M"; + } +} + +static void prph_report_phy(int64_t end_time, int64_t start_time, + uint32_t bytes, uint32_t packets, + const char *phy_name) +{ + if (packets == 0 || start_time == 0) { + return; + } + int64_t elapsed_ms = (end_time - start_time) / 1000; + if (elapsed_ms == 0) { elapsed_ms = 1; } + uint32_t kbps = (uint32_t)((uint64_t)bytes * 8ULL + / (uint64_t)elapsed_ms); + ESP_LOGI(TAG, "+-------------------------------------------------+"); + ESP_LOGI(TAG, "| PHY : %-39s|", phy_name); + ESP_LOGI(TAG, "| RX : %-6" PRIu32 " kbps |", kbps); + ESP_LOGI(TAG, "| Bytes : %-10" PRIu32 " |", bytes); + ESP_LOGI(TAG, "| Time : %-5lld s |", elapsed_ms / 1000); + ESP_LOGI(TAG, "+-------------------------------------------------+"); +} + +#if CONFIG_EXAMPLE_EXTENDED_ADV +static uint8_t ext_adv_pattern[] = { + 0x02, BLE_HS_ADV_TYPE_FLAGS, 0x06, + 0x03, BLE_HS_ADV_TYPE_COMP_UUIDS16, 0x12, 0x18, + 0x11, BLE_HS_ADV_TYPE_COMP_NAME, + 'l','2','c','a','p','-','c','o','c','-','p','r','p','h','-','e', +}; + +static void prph_advertise(void) +{ + struct ble_gap_ext_adv_params params; + struct os_mbuf *data; + uint8_t instance = 0; + int rc; + + memset(¶ms, 0, sizeof(params)); + params.connectable = 1; + params.own_addr_type = own_addr_type; + params.primary_phy = BLE_HCI_LE_PHY_1M; + params.secondary_phy = BLE_HCI_LE_PHY_1M; + params.tx_power = 127; + params.sid = 1; + params.itvl_min = BLE_GAP_ADV_FAST_INTERVAL1_MIN; + params.itvl_max = BLE_GAP_ADV_FAST_INTERVAL1_MIN; + + rc = ble_gap_ext_adv_configure(instance, ¶ms, NULL, prph_gap_event, NULL); + if (rc != 0) { + ESP_LOGE(TAG, "ext_adv_configure failed; rc=%d", rc); + return; + } + + data = os_msys_get_pkthdr(sizeof(ext_adv_pattern), 0); + if (!data) { + ESP_LOGE(TAG, "ext_adv: failed to alloc adv data mbuf"); + return; + } + rc = os_mbuf_append(data, ext_adv_pattern, sizeof(ext_adv_pattern)); + if (rc != 0) { + ESP_LOGE(TAG, "ext_adv: mbuf_append failed; rc=%d", rc); + os_mbuf_free_chain(data); + return; + } + + rc = ble_gap_ext_adv_set_data(instance, data); + if (rc != 0) { + ESP_LOGE(TAG, "ext_adv_set_data failed; rc=%d", rc); + return; + } + + rc = ble_gap_ext_adv_start(instance, 0, 0); + if (rc != 0) { + ESP_LOGE(TAG, "ext_adv_start failed; rc=%d", rc); + return; + } + ESP_LOGI(TAG, "Extended advertising started"); +} +#else +static void prph_advertise(void) +{ + struct ble_gap_adv_params adv_params; + struct ble_hs_adv_fields fields; + int rc; + + memset(&fields, 0, sizeof(fields)); + fields.flags = BLE_HS_ADV_F_DISC_GEN | BLE_HS_ADV_F_BREDR_UNSUP; + fields.tx_pwr_lvl_is_present = 1; + fields.tx_pwr_lvl = BLE_HS_ADV_TX_PWR_LVL_AUTO; +#if CONFIG_BT_NIMBLE_GAP_SERVICE + const char *name = ble_svc_gap_device_name(); + fields.name = (uint8_t *)name; + fields.name_len = strlen(name); + fields.name_is_complete = 1; +#endif + fields.uuids16 = (ble_uuid16_t[]){ BLE_UUID16_INIT(L2CAP_COC_UUID) }; + fields.num_uuids16 = 1; + fields.uuids16_is_complete = 1; + + rc = ble_gap_adv_set_fields(&fields); + if (rc != 0) { + ESP_LOGE(TAG, "Error setting adv data; rc=%d", rc); + return; + } + + memset(&adv_params, 0, sizeof(adv_params)); + adv_params.conn_mode = BLE_GAP_CONN_MODE_UND; + adv_params.disc_mode = BLE_GAP_DISC_MODE_GEN; + rc = ble_gap_adv_start(own_addr_type, NULL, BLE_HS_FOREVER, + &adv_params, prph_gap_event, NULL); + if (rc != 0) { + ESP_LOGE(TAG, "Error starting adv; rc=%d", rc); + } +} +#endif /* CONFIG_EXAMPLE_EXTENDED_ADV */ + +static void prph_l2cap_coc_mem_init(void) +{ + int rc; + rc = os_mempool_init(&sdu_coc_mempool, COC_BUF_COUNT, SDU_BLOCK_SIZE, + sdu_coc_mem, "prph_coc_pool"); + assert(rc == 0); + rc = os_mbuf_pool_init(&sdu_os_mbuf_pool, &sdu_coc_mempool, SDU_BLOCK_SIZE, + COC_BUF_COUNT); + assert(rc == 0); +} + +static int prph_l2cap_coc_accept(struct ble_l2cap_chan *chan) +{ + struct os_mbuf *sdu_rx = os_mbuf_get_pkthdr(&sdu_os_mbuf_pool, 0); + if (!sdu_rx) { + return BLE_HS_ENOMEM; + } + int rc = ble_l2cap_recv_ready(chan, sdu_rx); + if (rc != 0) { + os_mbuf_free_chain(sdu_rx); + } + return rc; +} + +static int prph_l2cap_coc_event_cb(struct ble_l2cap_event *event, void *arg) +{ + switch (event->type) { + case BLE_L2CAP_EVENT_COC_CONNECTED: + if (event->connect.status != 0) { + ESP_LOGE(TAG, "L2CAP COC connect error: %d", event->connect.status); + return 0; + } + ESP_LOGI(TAG, "L2CAP COC connected, chan=%p", event->connect.chan); + coc_chan = event->connect.chan; + phy_start_time = 0; /* anchored on first data SDU, not connect */ + rx_bytes = 0; + rx_packets = 0; + coc_active = true; + phy_name = prph_phy_str(current_phy); + return 0; + + case BLE_L2CAP_EVENT_COC_DISCONNECTED: + coc_active = false; + coc_chan = NULL; + current_phy = BLE_HCI_LE_PHY_1M; + { + int64_t end = esp_timer_get_time(); + int64_t st = phy_start_time; + uint32_t by = rx_bytes; + uint32_t pk = rx_packets; + prph_report_phy(end, st, by, pk, phy_name); + rx_bytes = 0; rx_packets = 0; phy_start_time = 0; + } + ESP_LOGI(TAG, "L2CAP COC disconnected"); + return 0; + + case BLE_L2CAP_EVENT_COC_ACCEPT: { + /* Pre-grant 2 receive buffers so the central can pipeline 2 SDUs. */ + int rc = prph_l2cap_coc_accept(event->accept.chan); + if (rc != 0) { + return rc; + } + /* Second buffer is best-effort; one buffer is enough for the channel to operate. */ + if (prph_l2cap_coc_accept(event->accept.chan) != 0) { + ESP_LOGW(TAG, "L2CAP COC accept: second RX buffer unavailable, running with one"); + } + return 0; + } + + case BLE_L2CAP_EVENT_COC_DATA_RECEIVED: + if (event->receive.sdu_rx) { + if (rx_packets == 0) { + phy_start_time = esp_timer_get_time(); + } + rx_bytes += OS_MBUF_PKTLEN(event->receive.sdu_rx); + rx_packets += 1; + os_mbuf_free_chain(event->receive.sdu_rx); + } + if (prph_l2cap_coc_accept(event->receive.chan) != 0) { + ESP_LOGE(TAG, "DATA_RECEIVED: no mbuf for recv_ready; RX may stall"); + } + return 0; + + default: + return 0; + } +} + +static void prph_stats_task(void *arg) +{ + uint32_t prev_bytes = 0; + int64_t prev_time = 0; + + while (1) { + vTaskDelay(pdMS_TO_TICKS(1000)); + + if (!coc_active) { + prev_bytes = 0; + prev_time = 0; + continue; + } + + int64_t now = esp_timer_get_time(); + uint32_t bytes = rx_bytes; + + if (prev_time > 0) { + if (bytes < prev_bytes) { + prev_bytes = bytes; + prev_time = now; + continue; + } + int64_t dt_us = now - prev_time; + uint32_t dt_bytes = bytes - prev_bytes; + uint32_t kbps = (uint32_t)((uint64_t)dt_bytes * 8ULL * 1000000ULL + / (uint64_t)dt_us / 1000ULL); + ESP_LOGI(TAG, "| RX : %-6" PRIu32 " kbps |", kbps); + } + + prev_bytes = bytes; + prev_time = now; + } +} + +static int prph_gap_event(struct ble_gap_event *event, void *arg) +{ + switch (event->type) { + case BLE_GAP_EVENT_CONNECT: + if (event->connect.status != 0) { + ESP_LOGE(TAG, "Connection failed; status=%d", event->connect.status); + prph_advertise(); + return 0; + } + ESP_LOGI(TAG, "Connected; handle=%d", event->connect.conn_handle); + conn_handle = event->connect.conn_handle; + return 0; + + case BLE_GAP_EVENT_DISCONNECT: + ESP_LOGI(TAG, "Disconnected; reason=%d", event->disconnect.reason); + conn_handle = BLE_HS_CONN_HANDLE_NONE; + coc_chan = NULL; + coc_active = false; + current_phy = BLE_HCI_LE_PHY_1M; + phy_name = "1M"; +#if CONFIG_EXAMPLE_EXTENDED_ADV + ble_gap_ext_adv_stop(0); +#endif + prph_advertise(); + return 0; + + case BLE_GAP_EVENT_PHY_UPDATE_COMPLETE: + ESP_LOGI(TAG, "PHY updated: tx=%d rx=%d status=%d", + event->phy_updated.tx_phy, + event->phy_updated.rx_phy, + event->phy_updated.status); + if (event->phy_updated.status == 0) { + if (coc_active) { + int64_t end = esp_timer_get_time(); + int64_t st = phy_start_time; + uint32_t by = rx_bytes; + uint32_t pk = rx_packets; + prph_report_phy(end, st, by, pk, phy_name); + rx_bytes = 0; rx_packets = 0; phy_start_time = 0; + } + current_phy = event->phy_updated.rx_phy; + phy_name = prph_phy_str(event->phy_updated.rx_phy); + } + return 0; + + case BLE_GAP_EVENT_CONN_UPDATE: + ESP_LOGI(TAG, "Conn params updated; status=%d", event->conn_update.status); + if (event->conn_update.status == 0 && coc_active && + current_phy == BLE_HCI_LE_PHY_CODED) { + struct ble_gap_conn_desc desc; + if (ble_gap_conn_find(conn_handle, &desc) == 0) { + if (desc.conn_itvl != 16 && desc.conn_itvl != 32) { + return 0; + } + if (strcmp(phy_name, "Coded") != 0) { + int64_t end = esp_timer_get_time(); + uint32_t by = rx_bytes; + uint32_t pk = rx_packets; + prph_report_phy(end, phy_start_time, by, pk, phy_name); + rx_bytes = 0; rx_packets = 0; phy_start_time = 0; + } + phy_name = (desc.conn_itvl >= 32) ? "Coded S8" : "Coded S2"; + ESP_LOGI(TAG, "Coding scheme updated to %s (CI=%u × 1.25ms)", + phy_name, desc.conn_itvl); + } + } + return 0; + + case BLE_GAP_EVENT_ADV_COMPLETE: +#if !CONFIG_EXAMPLE_EXTENDED_ADV + prph_advertise(); +#endif + return 0; + + default: + return 0; + } +} + +static void prph_on_reset(int reason) +{ + ESP_LOGE(TAG, "Host reset; reason=%d", reason); +} + +static void prph_on_sync(void) +{ + int rc; + + rc = ble_hs_util_ensure_addr(0); + assert(rc == 0); + + rc = ble_hs_id_infer_auto(0, &own_addr_type); + assert(rc == 0); + + rc = ble_l2cap_create_server(L2CAP_COC_PSM, L2CAP_COC_MTU, + prph_l2cap_coc_event_cb, NULL); + if (rc != 0 && rc != BLE_HS_EALREADY) { + ESP_LOGE(TAG, "Failed to create L2CAP COC server; rc=%d", rc); + return; + } + + uint8_t addr[6] = {0}; + ble_hs_id_copy_addr(own_addr_type, addr, NULL); + ESP_LOGI(TAG, "Device Address: %02x:%02x:%02x:%02x:%02x:%02x", + addr[5], addr[4], addr[3], addr[2], addr[1], addr[0]); + + prph_advertise(); +} + +static void prph_host_task(void *param) +{ + ESP_LOGI(TAG, "BLE Host Task started"); + nimble_port_run(); + nimble_port_freertos_deinit(); +} + +void app_main(void) +{ + esp_err_t 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); + + ret = nimble_port_init(); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "nimble_port_init failed; rc=%d", ret); + return; + } + + prph_l2cap_coc_mem_init(); + + ble_hs_cfg.reset_cb = prph_on_reset; + ble_hs_cfg.sync_cb = prph_on_sync; + ble_hs_cfg.store_status_cb = ble_store_util_status_rr; + +#if CONFIG_BT_NIMBLE_GAP_SERVICE + int rc = ble_svc_gap_device_name_set("l2cap-coc-prph"); + assert(rc == 0); +#endif + + ble_store_config_init(); + + if (xTaskCreate(prph_stats_task, "prph_stats", 4096, NULL, 5, NULL) != pdPASS) { + ESP_LOGE(TAG, "Failed to create stats task"); + } + + nimble_port_freertos_init(prph_host_task); +} diff --git a/examples/bluetooth/nimble/throughput_app/l2cap_coc/l2cap_coc_prph/sdkconfig.defaults b/examples/bluetooth/nimble/throughput_app/l2cap_coc/l2cap_coc_prph/sdkconfig.defaults new file mode 100644 index 00000000000..441f5c6c3a2 --- /dev/null +++ b/examples/bluetooth/nimble/throughput_app/l2cap_coc/l2cap_coc_prph/sdkconfig.defaults @@ -0,0 +1,15 @@ +CONFIG_BT_ENABLED=y +CONFIG_BT_NIMBLE_ENABLED=y +CONFIG_BT_NIMBLE_ATT_PREFERRED_MTU=512 +CONFIG_BT_NIMBLE_TRANSPORT_EVT_SIZE=255 +CONFIG_BT_NIMBLE_LOG_LEVEL=4 +CONFIG_BT_NIMBLE_LOG_LEVEL_NONE=y +CONFIG_BT_NIMBLE_MSYS_1_BLOCK_COUNT=400 +CONFIG_BT_NIMBLE_MSYS_1_BLOCK_SIZE=255 +CONFIG_BT_NIMBLE_MSYS_2_BLOCK_COUNT=50 +CONFIG_BT_NIMBLE_MSYS_2_BLOCK_SIZE=260 +CONFIG_BT_NIMBLE_L2CAP_COC_MAX_NUM=1 +CONFIG_BT_NIMBLE_L2CAP_COC_SDU_BUFF_COUNT=12 +CONFIG_BT_NIMBLE_TRANSPORT_ACL_FROM_LL_COUNT=24 +CONFIG_FREERTOS_HZ=1000 +CONFIG_ESP_TASK_WDT_TIMEOUT_S=30 diff --git a/examples/bluetooth/nimble/throughput_app/l2cap_coc/l2cap_coc_prph/sdkconfig.defaults.esp32 b/examples/bluetooth/nimble/throughput_app/l2cap_coc/l2cap_coc_prph/sdkconfig.defaults.esp32 new file mode 100644 index 00000000000..796c5e2681c --- /dev/null +++ b/examples/bluetooth/nimble/throughput_app/l2cap_coc/l2cap_coc_prph/sdkconfig.defaults.esp32 @@ -0,0 +1,4 @@ +CONFIG_EXAMPLE_L2CAP_COC_MTU=8192 +CONFIG_BT_NIMBLE_MSYS_1_BLOCK_COUNT=20 +CONFIG_BT_NIMBLE_MSYS_2_BLOCK_COUNT=20 +CONFIG_BT_NIMBLE_TRANSPORT_ACL_FROM_LL_COUNT=10 diff --git a/examples/bluetooth/nimble/throughput_app/l2cap_coc/l2cap_coc_prph/sdkconfig.defaults.esp32c2 b/examples/bluetooth/nimble/throughput_app/l2cap_coc/l2cap_coc_prph/sdkconfig.defaults.esp32c2 new file mode 100644 index 00000000000..3d6eecb81ac --- /dev/null +++ b/examples/bluetooth/nimble/throughput_app/l2cap_coc/l2cap_coc_prph/sdkconfig.defaults.esp32c2 @@ -0,0 +1,7 @@ +# MTU is intentionally left at the Kconfig default (16384) for throughput. +# Reducing to 2048 cuts credits from ~67 to ~9, dropping throughput ~8x. +# If the build fails due to RAM pressure (~96 KB static SDU pool), lower MTU here. +CONFIG_BT_NIMBLE_MSYS_1_BLOCK_COUNT=12 +CONFIG_BT_NIMBLE_MSYS_1_BLOCK_SIZE=292 +CONFIG_BT_NIMBLE_MSYS_2_BLOCK_COUNT=0 +CONFIG_BT_NIMBLE_TRANSPORT_ACL_FROM_LL_COUNT=24 diff --git a/examples/bluetooth/nimble/throughput_app/l2cap_coc/l2cap_coc_prph/sdkconfig.defaults.esp32c3 b/examples/bluetooth/nimble/throughput_app/l2cap_coc/l2cap_coc_prph/sdkconfig.defaults.esp32c3 new file mode 100644 index 00000000000..77211f1473e --- /dev/null +++ b/examples/bluetooth/nimble/throughput_app/l2cap_coc/l2cap_coc_prph/sdkconfig.defaults.esp32c3 @@ -0,0 +1,6 @@ +# MTU is intentionally left at the Kconfig default (16384) for throughput. +# Reducing MTU lowers L2CAP credit flow, which directly cuts throughput. +# If the build fails due to RAM pressure (~98 KB static SDU pool), lower MTU here. +CONFIG_BT_NIMBLE_MSYS_1_BLOCK_COUNT=20 +CONFIG_BT_NIMBLE_TRANSPORT_ACL_FROM_LL_COUNT=24 +CONFIG_BT_CTRL_BLE_STATIC_ACL_TX_BUF_NB=8 diff --git a/examples/bluetooth/nimble/throughput_app/l2cap_coc/l2cap_coc_prph/sdkconfig.defaults.esp32c6 b/examples/bluetooth/nimble/throughput_app/l2cap_coc/l2cap_coc_prph/sdkconfig.defaults.esp32c6 new file mode 100644 index 00000000000..a9588a5cd15 --- /dev/null +++ b/examples/bluetooth/nimble/throughput_app/l2cap_coc/l2cap_coc_prph/sdkconfig.defaults.esp32c6 @@ -0,0 +1,2 @@ +CONFIG_BT_NIMBLE_MSYS_1_BLOCK_COUNT=30 +CONFIG_BT_NIMBLE_TRANSPORT_ACL_FROM_LL_COUNT=67 diff --git a/examples/bluetooth/nimble/throughput_app/l2cap_coc/l2cap_coc_prph/sdkconfig.defaults.esp32h2 b/examples/bluetooth/nimble/throughput_app/l2cap_coc/l2cap_coc_prph/sdkconfig.defaults.esp32h2 new file mode 100644 index 00000000000..a9588a5cd15 --- /dev/null +++ b/examples/bluetooth/nimble/throughput_app/l2cap_coc/l2cap_coc_prph/sdkconfig.defaults.esp32h2 @@ -0,0 +1,2 @@ +CONFIG_BT_NIMBLE_MSYS_1_BLOCK_COUNT=30 +CONFIG_BT_NIMBLE_TRANSPORT_ACL_FROM_LL_COUNT=67 From 5dfb38a6d33abe397b468c1ea720f161a3ee4178 Mon Sep 17 00:00:00 2001 From: Sumeet Singh Date: Fri, 26 Jun 2026 12:24:34 +0530 Subject: [PATCH 13/13] fix(nimble): Add npl locks to avoid race condition (v6.0) --- .../host/nimble/esp-hci/src/esp_nimble_hci.c | 3 - components/bt/host/nimble/nimble | 2 +- .../npl/freertos/src/npl_os_freertos.c | 303 +++++++++++++++--- 3 files changed, 265 insertions(+), 43 deletions(-) diff --git a/components/bt/host/nimble/esp-hci/src/esp_nimble_hci.c b/components/bt/host/nimble/esp-hci/src/esp_nimble_hci.c index e81e9d481d9..125486965d9 100644 --- a/components/bt/host/nimble/esp-hci/src/esp_nimble_hci.c +++ b/components/bt/host/nimble/esp-hci/src/esp_nimble_hci.c @@ -182,7 +182,6 @@ static void ble_hci_rx_acl(uint8_t *data, uint16_t len) { struct os_mbuf *m = NULL; int rc; - int sr; int retry_count = 1; @@ -216,9 +215,7 @@ static void ble_hci_rx_acl(uint8_t *data, uint16_t len) os_mbuf_free_chain(m); return; } - OS_ENTER_CRITICAL(sr); ble_transport_to_hs_acl(m); - OS_EXIT_CRITICAL(sr); } #endif diff --git a/components/bt/host/nimble/nimble b/components/bt/host/nimble/nimble index 685675c0128..bdc5010548e 160000 --- a/components/bt/host/nimble/nimble +++ b/components/bt/host/nimble/nimble @@ -1 +1 @@ -Subproject commit 685675c0128deafdd201c9eb82e61d227364646c +Subproject commit bdc5010548e988a770adb8af107d01feb850c0ca diff --git a/components/bt/porting/npl/freertos/src/npl_os_freertos.c b/components/bt/porting/npl/freertos/src/npl_os_freertos.c index d4b8fc883c5..dcc09ce2628 100644 --- a/components/bt/porting/npl/freertos/src/npl_os_freertos.c +++ b/components/bt/porting/npl/freertos/src/npl_os_freertos.c @@ -25,6 +25,8 @@ portMUX_TYPE ble_port_mutex = portMUX_INITIALIZER_UNLOCKED; +static SemaphoreHandle_t npl_eventq_sync; + #if BLE_NPL_USE_ESP_TIMER static const char *TAG = "Timer"; #endif @@ -197,6 +199,115 @@ IRAM_ATTR in_isr(void) return xPortInIsrContext() != 0; } +static void +npl_eventq_sync_init(void) +{ + if (npl_eventq_sync == NULL) { + npl_eventq_sync = xSemaphoreCreateMutex(); + BLE_LL_ASSERT(npl_eventq_sync); + } +} + +static void +npl_eventq_lock(void) +{ + if (!in_isr()) { + BLE_LL_ASSERT(npl_eventq_sync); + xSemaphoreTake(npl_eventq_sync, portMAX_DELAY); + } +} + +static void +npl_eventq_unlock(void) +{ + if (!in_isr()) { + xSemaphoreGive(npl_eventq_sync); + } +} + +static bool IRAM_ATTR +npl_eventq_queued_get_isr(struct ble_npl_event_freertos *event) +{ + bool queued; + + portENTER_CRITICAL_ISR(&ble_port_mutex); + queued = event->queued; + portEXIT_CRITICAL_ISR(&ble_port_mutex); + return queued; +} + +static void IRAM_ATTR +npl_eventq_queued_set_isr(struct ble_npl_event_freertos *event, bool queued) +{ + portENTER_CRITICAL_ISR(&ble_port_mutex); + event->queued = queued; + portEXIT_CRITICAL_ISR(&ble_port_mutex); +} + +static bool IRAM_ATTR +npl_eventq_queued_claim_isr(struct ble_npl_event_freertos *event) +{ + bool already; + + portENTER_CRITICAL_ISR(&ble_port_mutex); + already = event->queued; + if (!already) { + event->queued = true; + } + portEXIT_CRITICAL_ISR(&ble_port_mutex); + return already; +} + +static void IRAM_ATTR +npl_eventq_queued_set_task(struct ble_npl_event_freertos *event, bool queued) +{ + portENTER_CRITICAL(&ble_port_mutex); + event->queued = queued; + portEXIT_CRITICAL(&ble_port_mutex); +} + +static bool IRAM_ATTR +npl_eventq_queued_get_task(struct ble_npl_event_freertos *event) +{ + bool queued; + + portENTER_CRITICAL(&ble_port_mutex); + queued = event->queued; + portEXIT_CRITICAL(&ble_port_mutex); + return queued; +} + +static bool IRAM_ATTR +npl_eventq_queued_claim(struct ble_npl_event_freertos *event) +{ + bool already; + + portENTER_CRITICAL(&ble_port_mutex); + already = event->queued; + if (!already) { + event->queued = true; + } + portEXIT_CRITICAL(&ble_port_mutex); + return already; +} + +static void IRAM_ATTR +npl_eventq_lost_event_clear(struct ble_npl_event *ev) +{ + struct ble_npl_event_freertos *lost; + + if (ev == NULL) { + return; + } + + lost = (struct ble_npl_event_freertos *)ev->event; + if (lost == NULL) { + return; + } + + lost->queued = false; +} + struct ble_npl_event * IRAM_ATTR npl_freertos_eventq_get(struct ble_npl_eventq *evq, ble_npl_time_t tmo) { @@ -211,16 +322,63 @@ IRAM_ATTR npl_freertos_eventq_get(struct ble_npl_eventq *evq, ble_npl_time_t tmo if( woken == pdTRUE ) { portYIELD_FROM_ISR(); } - } else { - ret = xQueueReceive(eventq->q, &ev, tmo); - } - BLE_LL_ASSERT(ret == pdPASS || ret == errQUEUE_EMPTY); + BLE_LL_ASSERT(ret == pdPASS || ret == errQUEUE_EMPTY); - if (ev) { - struct ble_npl_event_freertos *event = (struct ble_npl_event_freertos *)ev->event; - if (event) { - event->queued = false; - } + if (ev) { + struct ble_npl_event_freertos *event = (struct ble_npl_event_freertos *)ev->event; + if (event) { + npl_eventq_queued_set_isr(event, false); + } + } + } else if (tmo == 0) { + npl_eventq_lock(); + portENTER_CRITICAL(&ble_port_mutex); + ret = xQueueReceive(eventq->q, &ev, 0); + if (ret == pdPASS && ev != NULL) { + struct ble_npl_event_freertos *event = (struct ble_npl_event_freertos *)ev->event; + if (event) { + event->queued = false; + } + } + portEXIT_CRITICAL(&ble_port_mutex); + npl_eventq_unlock(); + } else { + TickType_t deadline = 0; + TickType_t remaining; + + if (tmo != portMAX_DELAY) { + deadline = xTaskGetTickCount() + tmo; + } + + for (;;) { + if (tmo == portMAX_DELAY) { + ret = xQueuePeek(eventq->q, &ev, portMAX_DELAY); + } else { + remaining = deadline - xTaskGetTickCount(); + if (remaining > tmo) { + return NULL; + } + ret = xQueuePeek(eventq->q, &ev, remaining); + } + if (ret != pdPASS) { + return NULL; + } + + npl_eventq_lock(); + portENTER_CRITICAL(&ble_port_mutex); + ret = xQueueReceive(eventq->q, &ev, 0); + if (ret == pdPASS && ev != NULL) { + struct ble_npl_event_freertos *event = (struct ble_npl_event_freertos *)ev->event; + if (event) { + event->queued = false; + } + portEXIT_CRITICAL(&ble_port_mutex); + npl_eventq_unlock(); + break; + } + portEXIT_CRITICAL(&ble_port_mutex); + npl_eventq_unlock(); + } } return ev; @@ -234,22 +392,35 @@ IRAM_ATTR npl_freertos_eventq_put(struct ble_npl_eventq *evq, struct ble_npl_eve struct ble_npl_eventq_freertos *eventq = (struct ble_npl_eventq_freertos *)evq->eventq; struct ble_npl_event_freertos *event = (struct ble_npl_event_freertos *)ev->event; - if (event->queued) { - return; - } - - event->queued = true; - if (in_isr()) { + if (npl_eventq_queued_claim_isr(event)) { + return; + } + ret = xQueueSendToBackFromISR(eventq->q, &ev, &woken); + if (ret != pdPASS) { + npl_eventq_queued_set_isr(event, false); + return; + } if( woken == pdTRUE ) { portYIELD_FROM_ISR(); } + return; } else { - ret = xQueueSendToBack(eventq->q, &ev, portMAX_DELAY); - } + npl_eventq_lock(); - BLE_LL_ASSERT(ret == pdPASS); + if (npl_eventq_queued_claim(event)) { + npl_eventq_unlock(); + return; + } + + ret = xQueueSendToBack(eventq->q, &ev, 0); + if (ret != pdPASS) { + ESP_LOGW("NimBLE", "eventq put: queue full, event dropped"); + npl_eventq_queued_set_task(event, false); + } + npl_eventq_unlock(); + } } void @@ -260,22 +431,35 @@ IRAM_ATTR npl_freertos_eventq_put_to_front(struct ble_npl_eventq *evq, struct bl struct ble_npl_eventq_freertos *eventq = (struct ble_npl_eventq_freertos *)evq->eventq; struct ble_npl_event_freertos *event = (struct ble_npl_event_freertos *)ev->event; - if (event->queued) { - return; - } - - event->queued = true; - if (in_isr()) { + if (npl_eventq_queued_claim_isr(event)) { + return; + } + ret = xQueueSendToFrontFromISR(eventq->q, &ev, &woken); + if (ret != pdPASS) { + npl_eventq_queued_set_isr(event, false); + return; + } if( woken == pdTRUE ) { portYIELD_FROM_ISR(); } + return; } else { - ret = xQueueSendToFront(eventq->q, &ev, portMAX_DELAY); - } + npl_eventq_lock(); - BLE_LL_ASSERT(ret == pdPASS); + if (npl_eventq_queued_claim(event)) { + npl_eventq_unlock(); + return; + } + + ret = xQueueSendToFront(eventq->q, &ev, 0); + if (ret != pdPASS) { + ESP_LOGW("NimBLE", "eventq put_to_front: queue full, event dropped"); + npl_eventq_queued_set_task(event, false); + } + npl_eventq_unlock(); + } } void @@ -286,14 +470,11 @@ IRAM_ATTR npl_freertos_eventq_remove(struct ble_npl_eventq *evq, BaseType_t ret; int i; int count; + bool removed; BaseType_t woken, woken2; struct ble_npl_eventq_freertos *eventq = (struct ble_npl_eventq_freertos *)evq->eventq; struct ble_npl_event_freertos *event = (struct ble_npl_event_freertos *)ev->event; - if (!event->queued) { - return; - } - /* * XXX We cannot extract element from inside FreeRTOS queue so as a quick * workaround we'll just remove all elements and add them back except the @@ -302,46 +483,77 @@ IRAM_ATTR npl_freertos_eventq_remove(struct ble_npl_eventq *evq, */ if (in_isr()) { + if (!npl_eventq_queued_get_isr(event)) { + return; + } + + removed = false; woken = pdFALSE; + portENTER_CRITICAL_ISR(&ble_port_mutex); count = uxQueueMessagesWaitingFromISR(eventq->q); for (i = 0; i < count; i++) { ret = xQueueReceiveFromISR(eventq->q, &tmp_ev, &woken2); - BLE_LL_ASSERT(ret == pdPASS); + if (ret != pdPASS) { + break; + } woken |= woken2; if (tmp_ev == ev) { + removed = true; continue; } ret = xQueueSendToBackFromISR(eventq->q, &tmp_ev, &woken2); - BLE_LL_ASSERT(ret == pdPASS); + if (ret != pdPASS) { + npl_eventq_lost_event_clear(tmp_ev); + break; + } woken |= woken2; } + if (removed) { + event->queued = false; + } + portEXIT_CRITICAL_ISR(&ble_port_mutex); if( woken == pdTRUE ) { portYIELD_FROM_ISR(); } } else { - portENTER_CRITICAL(&ble_port_mutex); + removed = false; + npl_eventq_lock(); + if (!npl_eventq_queued_get_task(event)) { + npl_eventq_unlock(); + return; + } + + portENTER_CRITICAL(&ble_port_mutex); count = uxQueueMessagesWaiting(eventq->q); for (i = 0; i < count; i++) { ret = xQueueReceive(eventq->q, &tmp_ev, 0); - BLE_LL_ASSERT(ret == pdPASS); + if (ret != pdPASS) { + break; + } if (tmp_ev == ev) { + removed = true; continue; } ret = xQueueSendToBack(eventq->q, &tmp_ev, 0); - BLE_LL_ASSERT(ret == pdPASS); + if (ret != pdPASS) { + npl_eventq_lost_event_clear(tmp_ev); + break; + } + } + if (removed) { + event->queued = 0; } - portEXIT_CRITICAL(&ble_port_mutex); - } - event->queued = 0; + npl_eventq_unlock(); + } } ble_npl_error_t @@ -1116,6 +1328,9 @@ int npl_freertos_set_controller_npl_info(ble_npl_count_info_t *ctrl_npl_info) int npl_freertos_mempool_init(void) { int rc = -1; + + npl_eventq_sync_init(); + uint16_t ble_total_evt_count = 0; uint16_t ble_total_co_count = 0; uint16_t ble_total_evtq_count = 0; @@ -1205,6 +1420,11 @@ int npl_freertos_mempool_init(void) return 0; _error: + if (npl_eventq_sync) { + vSemaphoreDelete(npl_eventq_sync); + npl_eventq_sync = NULL; + } + if (ble_freertos_ev_buf) { bt_osi_mem_free_internal(ble_freertos_ev_buf); ble_freertos_ev_buf = NULL; @@ -1234,6 +1454,11 @@ _error: void npl_freertos_mempool_deinit(void) { + if (npl_eventq_sync) { + vSemaphoreDelete(npl_eventq_sync); + npl_eventq_sync = NULL; + } + if (ble_freertos_ev_buf) { bt_osi_mem_free_internal(ble_freertos_ev_buf); ble_freertos_ev_buf = NULL;