mirror of
https://github.com/espressif/esp-idf.git
synced 2026-10-01 10:40:47 +03:00
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:
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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, ×tamp, sizeof(ts_bytes));
|
||||
|
||||
offset = snprintf(line, sizeof(line), "%02x %s:", (unsigned int)seq_num, label);
|
||||
if (offset < 0 || (size_t)offset >= sizeof(line) - 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < sizeof(ts_bytes) && offset <= (int)sizeof(line) - 3; i++) {
|
||||
offset += snprintf(&line[offset], sizeof(line) - offset, "%02x", ts_bytes[i]);
|
||||
}
|
||||
|
||||
if (offset < 0 || (size_t)offset >= sizeof(line) - 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
line[offset++] = ' ';
|
||||
line[offset] = '\0';
|
||||
|
||||
for (uint16_t i = 0; i < data_len && offset <= (int)sizeof(line) - 3; i++) {
|
||||
offset += snprintf(&line[offset], sizeof(line) - offset, "%02X", data[i]);
|
||||
}
|
||||
|
||||
ble_insights_log(line);
|
||||
}
|
||||
#endif
|
||||
@@ -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__ */
|
||||
@@ -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__ */
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
Submodule components/bt/host/nimble/nimble updated: 5d8b70207a...bdc5010548
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
+4
-3
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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 |
|
||||
|
||||
+2
-1
@@ -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)) {
|
||||
+3
@@ -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);
|
||||
+5
-21
@@ -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;
|
||||
}
|
||||
+12
-1
@@ -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.
|
||||
*/
|
||||
-3
@@ -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
|
||||
|
||||
-3
@@ -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 ".")
|
||||
+67
@@ -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
|
||||
+3
@@ -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
|
||||
+4
@@ -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
|
||||
+6
@@ -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
|
||||
+4
@@ -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
|
||||
+3
@@ -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
|
||||
+3
@@ -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 ".")
|
||||
+23
@@ -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(¶ms, 0, sizeof(params));
|
||||
params.connectable = 1;
|
||||
params.own_addr_type = own_addr_type;
|
||||
params.primary_phy = BLE_HCI_LE_PHY_1M;
|
||||
params.secondary_phy = BLE_HCI_LE_PHY_1M;
|
||||
params.tx_power = 127;
|
||||
params.sid = 1;
|
||||
params.itvl_min = BLE_GAP_ADV_FAST_INTERVAL1_MIN;
|
||||
params.itvl_max = BLE_GAP_ADV_FAST_INTERVAL1_MIN;
|
||||
|
||||
rc = ble_gap_ext_adv_configure(instance, ¶ms, NULL, prph_gap_event, NULL);
|
||||
if (rc != 0) {
|
||||
ESP_LOGE(TAG, "ext_adv_configure failed; rc=%d", rc);
|
||||
return;
|
||||
}
|
||||
|
||||
data = os_msys_get_pkthdr(sizeof(ext_adv_pattern), 0);
|
||||
if (!data) {
|
||||
ESP_LOGE(TAG, "ext_adv: failed to alloc adv data mbuf");
|
||||
return;
|
||||
}
|
||||
rc = os_mbuf_append(data, ext_adv_pattern, sizeof(ext_adv_pattern));
|
||||
if (rc != 0) {
|
||||
ESP_LOGE(TAG, "ext_adv: mbuf_append failed; rc=%d", rc);
|
||||
os_mbuf_free_chain(data);
|
||||
return;
|
||||
}
|
||||
|
||||
rc = ble_gap_ext_adv_set_data(instance, data);
|
||||
if (rc != 0) {
|
||||
ESP_LOGE(TAG, "ext_adv_set_data failed; rc=%d", rc);
|
||||
return;
|
||||
}
|
||||
|
||||
rc = ble_gap_ext_adv_start(instance, 0, 0);
|
||||
if (rc != 0) {
|
||||
ESP_LOGE(TAG, "ext_adv_start failed; rc=%d", rc);
|
||||
return;
|
||||
}
|
||||
ESP_LOGI(TAG, "Extended advertising started");
|
||||
}
|
||||
#else
|
||||
static void prph_advertise(void)
|
||||
{
|
||||
struct ble_gap_adv_params adv_params;
|
||||
struct ble_hs_adv_fields fields;
|
||||
int rc;
|
||||
|
||||
memset(&fields, 0, sizeof(fields));
|
||||
fields.flags = BLE_HS_ADV_F_DISC_GEN | BLE_HS_ADV_F_BREDR_UNSUP;
|
||||
fields.tx_pwr_lvl_is_present = 1;
|
||||
fields.tx_pwr_lvl = BLE_HS_ADV_TX_PWR_LVL_AUTO;
|
||||
#if CONFIG_BT_NIMBLE_GAP_SERVICE
|
||||
const char *name = ble_svc_gap_device_name();
|
||||
fields.name = (uint8_t *)name;
|
||||
fields.name_len = strlen(name);
|
||||
fields.name_is_complete = 1;
|
||||
#endif
|
||||
fields.uuids16 = (ble_uuid16_t[]){ BLE_UUID16_INIT(L2CAP_COC_UUID) };
|
||||
fields.num_uuids16 = 1;
|
||||
fields.uuids16_is_complete = 1;
|
||||
|
||||
rc = ble_gap_adv_set_fields(&fields);
|
||||
if (rc != 0) {
|
||||
ESP_LOGE(TAG, "Error setting adv data; rc=%d", rc);
|
||||
return;
|
||||
}
|
||||
|
||||
memset(&adv_params, 0, sizeof(adv_params));
|
||||
adv_params.conn_mode = BLE_GAP_CONN_MODE_UND;
|
||||
adv_params.disc_mode = BLE_GAP_DISC_MODE_GEN;
|
||||
rc = ble_gap_adv_start(own_addr_type, NULL, BLE_HS_FOREVER,
|
||||
&adv_params, prph_gap_event, NULL);
|
||||
if (rc != 0) {
|
||||
ESP_LOGE(TAG, "Error starting adv; rc=%d", rc);
|
||||
}
|
||||
}
|
||||
#endif /* CONFIG_EXAMPLE_EXTENDED_ADV */
|
||||
|
||||
static void prph_l2cap_coc_mem_init(void)
|
||||
{
|
||||
int rc;
|
||||
rc = os_mempool_init(&sdu_coc_mempool, COC_BUF_COUNT, SDU_BLOCK_SIZE,
|
||||
sdu_coc_mem, "prph_coc_pool");
|
||||
assert(rc == 0);
|
||||
rc = os_mbuf_pool_init(&sdu_os_mbuf_pool, &sdu_coc_mempool, SDU_BLOCK_SIZE,
|
||||
COC_BUF_COUNT);
|
||||
assert(rc == 0);
|
||||
}
|
||||
|
||||
static int prph_l2cap_coc_accept(struct ble_l2cap_chan *chan)
|
||||
{
|
||||
struct os_mbuf *sdu_rx = os_mbuf_get_pkthdr(&sdu_os_mbuf_pool, 0);
|
||||
if (!sdu_rx) {
|
||||
return BLE_HS_ENOMEM;
|
||||
}
|
||||
int rc = ble_l2cap_recv_ready(chan, sdu_rx);
|
||||
if (rc != 0) {
|
||||
os_mbuf_free_chain(sdu_rx);
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int prph_l2cap_coc_event_cb(struct ble_l2cap_event *event, void *arg)
|
||||
{
|
||||
switch (event->type) {
|
||||
case BLE_L2CAP_EVENT_COC_CONNECTED:
|
||||
if (event->connect.status != 0) {
|
||||
ESP_LOGE(TAG, "L2CAP COC connect error: %d", event->connect.status);
|
||||
return 0;
|
||||
}
|
||||
ESP_LOGI(TAG, "L2CAP COC connected, chan=%p", event->connect.chan);
|
||||
coc_chan = event->connect.chan;
|
||||
phy_start_time = 0; /* anchored on first data SDU, not connect */
|
||||
rx_bytes = 0;
|
||||
rx_packets = 0;
|
||||
coc_active = true;
|
||||
phy_name = prph_phy_str(current_phy);
|
||||
return 0;
|
||||
|
||||
case BLE_L2CAP_EVENT_COC_DISCONNECTED:
|
||||
coc_active = false;
|
||||
coc_chan = NULL;
|
||||
current_phy = BLE_HCI_LE_PHY_1M;
|
||||
{
|
||||
int64_t end = esp_timer_get_time();
|
||||
int64_t st = phy_start_time;
|
||||
uint32_t by = rx_bytes;
|
||||
uint32_t pk = rx_packets;
|
||||
prph_report_phy(end, st, by, pk, phy_name);
|
||||
rx_bytes = 0; rx_packets = 0; phy_start_time = 0;
|
||||
}
|
||||
ESP_LOGI(TAG, "L2CAP COC disconnected");
|
||||
return 0;
|
||||
|
||||
case BLE_L2CAP_EVENT_COC_ACCEPT: {
|
||||
/* Pre-grant 2 receive buffers so the central can pipeline 2 SDUs. */
|
||||
int rc = prph_l2cap_coc_accept(event->accept.chan);
|
||||
if (rc != 0) {
|
||||
return rc;
|
||||
}
|
||||
/* Second buffer is best-effort; one buffer is enough for the channel to operate. */
|
||||
if (prph_l2cap_coc_accept(event->accept.chan) != 0) {
|
||||
ESP_LOGW(TAG, "L2CAP COC accept: second RX buffer unavailable, running with one");
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
case BLE_L2CAP_EVENT_COC_DATA_RECEIVED:
|
||||
if (event->receive.sdu_rx) {
|
||||
if (rx_packets == 0) {
|
||||
phy_start_time = esp_timer_get_time();
|
||||
}
|
||||
rx_bytes += OS_MBUF_PKTLEN(event->receive.sdu_rx);
|
||||
rx_packets += 1;
|
||||
os_mbuf_free_chain(event->receive.sdu_rx);
|
||||
}
|
||||
if (prph_l2cap_coc_accept(event->receive.chan) != 0) {
|
||||
ESP_LOGE(TAG, "DATA_RECEIVED: no mbuf for recv_ready; RX may stall");
|
||||
}
|
||||
return 0;
|
||||
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static void prph_stats_task(void *arg)
|
||||
{
|
||||
uint32_t prev_bytes = 0;
|
||||
int64_t prev_time = 0;
|
||||
|
||||
while (1) {
|
||||
vTaskDelay(pdMS_TO_TICKS(1000));
|
||||
|
||||
if (!coc_active) {
|
||||
prev_bytes = 0;
|
||||
prev_time = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
int64_t now = esp_timer_get_time();
|
||||
uint32_t bytes = rx_bytes;
|
||||
|
||||
if (prev_time > 0) {
|
||||
if (bytes < prev_bytes) {
|
||||
prev_bytes = bytes;
|
||||
prev_time = now;
|
||||
continue;
|
||||
}
|
||||
int64_t dt_us = now - prev_time;
|
||||
uint32_t dt_bytes = bytes - prev_bytes;
|
||||
uint32_t kbps = (uint32_t)((uint64_t)dt_bytes * 8ULL * 1000000ULL
|
||||
/ (uint64_t)dt_us / 1000ULL);
|
||||
ESP_LOGI(TAG, "| RX : %-6" PRIu32 " kbps |", kbps);
|
||||
}
|
||||
|
||||
prev_bytes = bytes;
|
||||
prev_time = now;
|
||||
}
|
||||
}
|
||||
|
||||
static int prph_gap_event(struct ble_gap_event *event, void *arg)
|
||||
{
|
||||
switch (event->type) {
|
||||
case BLE_GAP_EVENT_CONNECT:
|
||||
if (event->connect.status != 0) {
|
||||
ESP_LOGE(TAG, "Connection failed; status=%d", event->connect.status);
|
||||
prph_advertise();
|
||||
return 0;
|
||||
}
|
||||
ESP_LOGI(TAG, "Connected; handle=%d", event->connect.conn_handle);
|
||||
conn_handle = event->connect.conn_handle;
|
||||
return 0;
|
||||
|
||||
case BLE_GAP_EVENT_DISCONNECT:
|
||||
ESP_LOGI(TAG, "Disconnected; reason=%d", event->disconnect.reason);
|
||||
conn_handle = BLE_HS_CONN_HANDLE_NONE;
|
||||
coc_chan = NULL;
|
||||
coc_active = false;
|
||||
current_phy = BLE_HCI_LE_PHY_1M;
|
||||
phy_name = "1M";
|
||||
#if CONFIG_EXAMPLE_EXTENDED_ADV
|
||||
ble_gap_ext_adv_stop(0);
|
||||
#endif
|
||||
prph_advertise();
|
||||
return 0;
|
||||
|
||||
case BLE_GAP_EVENT_PHY_UPDATE_COMPLETE:
|
||||
ESP_LOGI(TAG, "PHY updated: tx=%d rx=%d status=%d",
|
||||
event->phy_updated.tx_phy,
|
||||
event->phy_updated.rx_phy,
|
||||
event->phy_updated.status);
|
||||
if (event->phy_updated.status == 0) {
|
||||
if (coc_active) {
|
||||
int64_t end = esp_timer_get_time();
|
||||
int64_t st = phy_start_time;
|
||||
uint32_t by = rx_bytes;
|
||||
uint32_t pk = rx_packets;
|
||||
prph_report_phy(end, st, by, pk, phy_name);
|
||||
rx_bytes = 0; rx_packets = 0; phy_start_time = 0;
|
||||
}
|
||||
current_phy = event->phy_updated.rx_phy;
|
||||
phy_name = prph_phy_str(event->phy_updated.rx_phy);
|
||||
}
|
||||
return 0;
|
||||
|
||||
case BLE_GAP_EVENT_CONN_UPDATE:
|
||||
ESP_LOGI(TAG, "Conn params updated; status=%d", event->conn_update.status);
|
||||
if (event->conn_update.status == 0 && coc_active &&
|
||||
current_phy == BLE_HCI_LE_PHY_CODED) {
|
||||
struct ble_gap_conn_desc desc;
|
||||
if (ble_gap_conn_find(conn_handle, &desc) == 0) {
|
||||
if (desc.conn_itvl != 16 && desc.conn_itvl != 32) {
|
||||
return 0;
|
||||
}
|
||||
if (strcmp(phy_name, "Coded") != 0) {
|
||||
int64_t end = esp_timer_get_time();
|
||||
uint32_t by = rx_bytes;
|
||||
uint32_t pk = rx_packets;
|
||||
prph_report_phy(end, phy_start_time, by, pk, phy_name);
|
||||
rx_bytes = 0; rx_packets = 0; phy_start_time = 0;
|
||||
}
|
||||
phy_name = (desc.conn_itvl >= 32) ? "Coded S8" : "Coded S2";
|
||||
ESP_LOGI(TAG, "Coding scheme updated to %s (CI=%u × 1.25ms)",
|
||||
phy_name, desc.conn_itvl);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
|
||||
case BLE_GAP_EVENT_ADV_COMPLETE:
|
||||
#if !CONFIG_EXAMPLE_EXTENDED_ADV
|
||||
prph_advertise();
|
||||
#endif
|
||||
return 0;
|
||||
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static void prph_on_reset(int reason)
|
||||
{
|
||||
ESP_LOGE(TAG, "Host reset; reason=%d", reason);
|
||||
}
|
||||
|
||||
static void prph_on_sync(void)
|
||||
{
|
||||
int rc;
|
||||
|
||||
rc = ble_hs_util_ensure_addr(0);
|
||||
assert(rc == 0);
|
||||
|
||||
rc = ble_hs_id_infer_auto(0, &own_addr_type);
|
||||
assert(rc == 0);
|
||||
|
||||
rc = ble_l2cap_create_server(L2CAP_COC_PSM, L2CAP_COC_MTU,
|
||||
prph_l2cap_coc_event_cb, NULL);
|
||||
if (rc != 0 && rc != BLE_HS_EALREADY) {
|
||||
ESP_LOGE(TAG, "Failed to create L2CAP COC server; rc=%d", rc);
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t addr[6] = {0};
|
||||
ble_hs_id_copy_addr(own_addr_type, addr, NULL);
|
||||
ESP_LOGI(TAG, "Device Address: %02x:%02x:%02x:%02x:%02x:%02x",
|
||||
addr[5], addr[4], addr[3], addr[2], addr[1], addr[0]);
|
||||
|
||||
prph_advertise();
|
||||
}
|
||||
|
||||
static void prph_host_task(void *param)
|
||||
{
|
||||
ESP_LOGI(TAG, "BLE Host Task started");
|
||||
nimble_port_run();
|
||||
nimble_port_freertos_deinit();
|
||||
}
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
esp_err_t ret = nvs_flash_init();
|
||||
if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
|
||||
ESP_ERROR_CHECK(nvs_flash_erase());
|
||||
ret = nvs_flash_init();
|
||||
}
|
||||
ESP_ERROR_CHECK(ret);
|
||||
|
||||
ret = nimble_port_init();
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "nimble_port_init failed; rc=%d", ret);
|
||||
return;
|
||||
}
|
||||
|
||||
prph_l2cap_coc_mem_init();
|
||||
|
||||
ble_hs_cfg.reset_cb = prph_on_reset;
|
||||
ble_hs_cfg.sync_cb = prph_on_sync;
|
||||
ble_hs_cfg.store_status_cb = ble_store_util_status_rr;
|
||||
|
||||
#if CONFIG_BT_NIMBLE_GAP_SERVICE
|
||||
int rc = ble_svc_gap_device_name_set("l2cap-coc-prph");
|
||||
assert(rc == 0);
|
||||
#endif
|
||||
|
||||
ble_store_config_init();
|
||||
|
||||
if (xTaskCreate(prph_stats_task, "prph_stats", 4096, NULL, 5, NULL) != pdPASS) {
|
||||
ESP_LOGE(TAG, "Failed to create stats task");
|
||||
}
|
||||
|
||||
nimble_port_freertos_init(prph_host_task);
|
||||
}
|
||||
@@ -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
|
||||
+4
@@ -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
|
||||
+7
@@ -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
|
||||
+6
@@ -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
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
CONFIG_BT_NIMBLE_MSYS_1_BLOCK_COUNT=30
|
||||
CONFIG_BT_NIMBLE_TRANSPORT_ACL_FROM_LL_COUNT=67
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
CONFIG_BT_NIMBLE_MSYS_1_BLOCK_COUNT=30
|
||||
CONFIG_BT_NIMBLE_TRANSPORT_ACL_FROM_LL_COUNT=67
|
||||
Reference in New Issue
Block a user