Merge branch 'bugfix/nimble_issues_02062026_v6.0' into 'release/v6.0'

fix(nimble):  Fix few nimble issues 02062026 (v6.0)

See merge request espressif/esp-idf!49153
This commit is contained in:
Rahul Tank
2026-06-29 21:52:48 +05:30
90 changed files with 2367 additions and 321 deletions
+6
View File
@@ -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
+1 -1
View File
@@ -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"
+15
View File
@@ -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)
+10
View File
@@ -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;
@@ -0,0 +1,90 @@
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#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, &timestamp, 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
@@ -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
@@ -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__ */
+14 -3
View File
@@ -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_ */
@@ -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__ */
+4 -7
View File
@@ -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
-17
View File
@@ -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))
@@ -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
@@ -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)
}
+8
View File
@@ -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
@@ -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);
@@ -178,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;
@@ -212,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
@@ -237,18 +238,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)
}
@@ -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
@@ -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;
+16 -1
View File
@@ -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
+132
View File
@@ -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;
}
+17 -3
View File
@@ -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;
+18 -6
View File
@@ -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/**/*
@@ -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
@@ -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;
}
@@ -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;
}
@@ -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
*/
@@ -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 <chip> 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
@@ -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
@@ -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;
}
@@ -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;
}
@@ -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;
}
@@ -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
@@ -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;
}
@@ -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) {
@@ -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)
@@ -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
@@ -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
@@ -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];
@@ -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;
}
+3 -26
View File
@@ -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);
@@ -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);
@@ -4,7 +4,21 @@
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
#include <stdio.h>
#include <string.h>
#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.
@@ -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 |
@@ -4,6 +4,7 @@
* SPDX-License-Identifier: Apache-2.0
*/
#include <inttypes.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
@@ -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)) {
@@ -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);
@@ -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;
}
@@ -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.
*/
@@ -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
@@ -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
@@ -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)
@@ -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 <chip_name>
```
### 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.
@@ -0,0 +1,3 @@
idf_component_register(SRCS "main.c"
PRIV_REQUIRES bt nvs_flash esp_timer
INCLUDE_DIRS ".")
@@ -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
@@ -0,0 +1,3 @@
dependencies:
nimble_central_utils:
path: ${IDF_PATH}/examples/bluetooth/nimble/common/nimble_central_utils
@@ -0,0 +1,738 @@
/*
* SPDX-FileCopyrightText: 2025-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdint.h>
#include <stdbool.h>
#include <string.h>
#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);
}
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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)
@@ -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 <chip_name>
```
### 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.
@@ -0,0 +1,3 @@
idf_component_register(SRCS "main.c"
PRIV_REQUIRES bt nvs_flash esp_timer
INCLUDE_DIRS ".")
@@ -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
@@ -0,0 +1,445 @@
/*
* SPDX-FileCopyrightText: 2025-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdint.h>
#include <stdbool.h>
#include <string.h>
#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(&params, 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, &params, 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);
}
@@ -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
@@ -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
@@ -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
@@ -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
@@ -0,0 +1,2 @@
CONFIG_BT_NIMBLE_MSYS_1_BLOCK_COUNT=30
CONFIG_BT_NIMBLE_TRANSPORT_ACL_FROM_LL_COUNT=67
@@ -0,0 +1,2 @@
CONFIG_BT_NIMBLE_MSYS_1_BLOCK_COUNT=30
CONFIG_BT_NIMBLE_TRANSPORT_ACL_FROM_LL_COUNT=67