mirror of
https://github.com/espressif/esp-idf.git
synced 2026-01-19 17:55:43 +00:00
feat(ble/bluedroid): Add new bluedroid host examples
This commit is contained in:
@@ -196,6 +196,26 @@ config BT_LE_POWER_CONTROL_ENABLED
|
||||
help
|
||||
Set this option to enable the Power Control feature on controller
|
||||
|
||||
config BT_LE_SUBRATE_ENABLED
|
||||
bool "Enable BLE connection subrating"
|
||||
depends on BT_LE_50_FEATURE_SUPPORT && !BT_NIMBLE_ENABLED && !BT_BLUEDROID_ENABLED && SOC_BLE_SUBRATE_SUPPORTED
|
||||
default n
|
||||
help
|
||||
Set this option to enable the Connection Subrating feature on controller
|
||||
|
||||
config BT_LE_CTE_FEATURE_ENABLED
|
||||
bool "Enable Bluetooth LE Direction Finding (AoA/AoD)"
|
||||
depends on BT_LE_50_FEATURE_SUPPORT && SOC_BLE_CTE_SUPPORTED && !(BT_LE_SECURITY_ENABLE)
|
||||
default n
|
||||
help
|
||||
Enable this option to activate Bluetooth LE Direction Finding (AoA/AoD) feature.
|
||||
Note:
|
||||
This feature allows devices to determine the direction of a Bluetooth CTE signal,
|
||||
enabling Angle of Arrival (AoA) and Angle of Departure (AoD) functionality.
|
||||
In chip esp32h2, Direction Finding is not supported in encrypted
|
||||
communication scenarios. If you are using chip esp32h2, ensure that encryption is
|
||||
disabled when using this feature.
|
||||
|
||||
config BT_LE_PERIODIC_ADV_WITH_RESPONSE_ENABLED
|
||||
bool "Enable BLE periodic advertising with response"
|
||||
depends on BT_LE_50_FEATURE_SUPPORT && SOC_BLE_PERIODIC_ADV_WITH_RESPONSE
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
@@ -40,6 +40,7 @@ extern "C" {
|
||||
#define DEFAULT_BT_LE_HCI_EVT_HI_BUF_COUNT MYNEWT_VAL(BLE_TRANSPORT_EVT_COUNT)
|
||||
#define DEFAULT_BT_LE_HCI_EVT_LO_BUF_COUNT MYNEWT_VAL(BLE_TRANSPORT_EVT_DISCARDABLE_COUNT)
|
||||
#define DEFAULT_BT_LE_POWER_CONTROL_ENABLED MYNEWT_VAL(BLE_POWER_CONTROL)
|
||||
#define DEFAULT_BT_LE_SUBRATE_ENABLED MYNEWT_VAL(BLE_CONN_SUBRATING)
|
||||
#if defined(CONFIG_BT_NIMBLE_50_FEATURE_SUPPORT)
|
||||
#define DEFAULT_BT_LE_50_FEATURE_SUPPORT (1)
|
||||
#else
|
||||
@@ -135,6 +136,13 @@ extern "C" {
|
||||
#else
|
||||
#define DEFAULT_BT_LE_POWER_CONTROL_ENABLED (0)
|
||||
#endif
|
||||
#if defined(CONFIG_BT_BLE_FEAT_CONN_SUBRATING)
|
||||
#define DEFAULT_BT_LE_SUBRATE_ENABLED (CONFIG_BT_BLE_FEAT_CONN_SUBRATING)
|
||||
#elif defined(CONFIG_BT_LE_SUBRATE_ENABLED)
|
||||
#define DEFAULT_BT_LE_SUBRATE_ENABLED (CONFIG_BT_LE_SUBRATE_ENABLED)
|
||||
#else
|
||||
#define DEFAULT_BT_LE_SUBRATE_ENABLED (0)
|
||||
#endif
|
||||
#if defined(CONFIG_BT_LE_50_FEATURE_SUPPORT)
|
||||
#define DEFAULT_BT_LE_50_FEATURE_SUPPORT (1)
|
||||
#else
|
||||
|
||||
@@ -227,6 +227,13 @@ config BT_LE_POWER_CONTROL_ENABLED
|
||||
help
|
||||
Set this option to enable the Power Control feature on controller
|
||||
|
||||
config BT_LE_SUBRATE_ENABLED
|
||||
bool "Enable BLE connection subrating"
|
||||
depends on BT_LE_50_FEATURE_SUPPORT && !BT_NIMBLE_ENABLED && !BT_BLUEDROID_ENABLED && SOC_BLE_SUBRATE_SUPPORTED
|
||||
default n
|
||||
help
|
||||
Set this option to enable the Connection Subrating feature on controller
|
||||
|
||||
config BT_LE_CTE_FEATURE_ENABLED
|
||||
bool "Enable Bluetooth LE Direction Finding (AoA/AoD)"
|
||||
depends on BT_LE_50_FEATURE_SUPPORT && SOC_BLE_CTE_SUPPORTED
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
@@ -136,6 +136,13 @@ extern "C" {
|
||||
#else
|
||||
#define DEFAULT_BT_LE_POWER_CONTROL_ENABLED (0)
|
||||
#endif
|
||||
#if defined(CONFIG_BT_BLE_FEAT_CONN_SUBRATING)
|
||||
#define DEFAULT_BT_LE_SUBRATE_ENABLED (CONFIG_BT_BLE_FEAT_CONN_SUBRATING)
|
||||
#elif defined(CONFIG_BT_LE_SUBRATE_ENABLED)
|
||||
#define DEFAULT_BT_LE_SUBRATE_ENABLED (CONFIG_BT_LE_SUBRATE_ENABLED)
|
||||
#else
|
||||
#define DEFAULT_BT_LE_SUBRATE_ENABLED (0)
|
||||
#endif
|
||||
#if defined(CONFIG_BT_LE_50_FEATURE_SUPPORT)
|
||||
#define DEFAULT_BT_LE_50_FEATURE_SUPPORT (1)
|
||||
#else
|
||||
@@ -157,7 +164,6 @@ extern "C" {
|
||||
#define DEFAULT_BT_LE_HCI_UART_RTS_PIN (-1)
|
||||
#endif
|
||||
|
||||
#define DEFAULT_BT_LE_SUBRATE_ENABLED 0
|
||||
#endif
|
||||
|
||||
#define DEFAULT_BT_LE_COEX_PHY_CODED_TX_RX_TLIM_EFF CONFIG_BT_LE_COEX_PHY_CODED_TX_RX_TLIM_EFF
|
||||
|
||||
@@ -227,6 +227,13 @@ config BT_LE_POWER_CONTROL_ENABLED
|
||||
help
|
||||
Set this option to enable the Power Control feature on controller
|
||||
|
||||
config BT_LE_SUBRATE_ENABLED
|
||||
bool "Enable BLE connection subrating"
|
||||
depends on BT_LE_50_FEATURE_SUPPORT && !BT_NIMBLE_ENABLED && !BT_BLUEDROID_ENABLED && SOC_BLE_SUBRATE_SUPPORTED
|
||||
default n
|
||||
help
|
||||
Set this option to enable the Connection Subrating feature on controller
|
||||
|
||||
config BT_LE_CTE_FEATURE_ENABLED
|
||||
bool "Enable Bluetooth LE Direction Finding (AoA/AoD)"
|
||||
depends on BT_LE_50_FEATURE_SUPPORT && SOC_BLE_CTE_SUPPORTED && !(BT_LE_SECURITY_ENABLE)
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
@@ -40,6 +40,7 @@ extern "C" {
|
||||
#define DEFAULT_BT_LE_HCI_EVT_HI_BUF_COUNT MYNEWT_VAL(BLE_TRANSPORT_EVT_COUNT)
|
||||
#define DEFAULT_BT_LE_HCI_EVT_LO_BUF_COUNT MYNEWT_VAL(BLE_TRANSPORT_EVT_DISCARDABLE_COUNT)
|
||||
#define DEFAULT_BT_LE_POWER_CONTROL_ENABLED MYNEWT_VAL(BLE_POWER_CONTROL)
|
||||
#define DEFAULT_BT_LE_SUBRATE_ENABLED MYNEWT_VAL(BLE_CONN_SUBRATING)
|
||||
#if defined(CONFIG_BT_NIMBLE_50_FEATURE_SUPPORT)
|
||||
#define DEFAULT_BT_LE_50_FEATURE_SUPPORT (1)
|
||||
#else
|
||||
@@ -135,6 +136,13 @@ extern "C" {
|
||||
#else
|
||||
#define DEFAULT_BT_LE_POWER_CONTROL_ENABLED (0)
|
||||
#endif
|
||||
#if defined(CONFIG_BT_BLE_FEAT_CONN_SUBRATING)
|
||||
#define DEFAULT_BT_LE_SUBRATE_ENABLED (CONFIG_BT_BLE_FEAT_CONN_SUBRATING)
|
||||
#elif defined(CONFIG_BT_LE_SUBRATE_ENABLED)
|
||||
#define DEFAULT_BT_LE_SUBRATE_ENABLED (CONFIG_BT_LE_SUBRATE_ENABLED)
|
||||
#else
|
||||
#define DEFAULT_BT_LE_SUBRATE_ENABLED (0)
|
||||
#endif
|
||||
#if defined(CONFIG_BT_LE_50_FEATURE_SUPPORT)
|
||||
#define DEFAULT_BT_LE_50_FEATURE_SUPPORT (1)
|
||||
#else
|
||||
|
||||
@@ -1534,7 +1534,7 @@ config BT_BLE_FEAT_CONN_SUBRATING
|
||||
|
||||
config BT_BLE_FEAT_PAWR_EN
|
||||
bool "Enable Periodic Advertisement with Response(PAwR)"
|
||||
depends on (BT_BLE_50_FEATURES_SUPPORTED && ((BT_CONTROLLER_ENABLED && SOC_BLE_PAWR_SUPPORTED) || BT_CONTROLLER_DISABLED)) # NOERROR
|
||||
depends on (BT_BLE_50_FEATURES_SUPPORTED && ((BT_CONTROLLER_ENABLED && SOC_BLE_PERIODIC_ADV_WITH_RESPONSE) || BT_CONTROLLER_DISABLED)) # NOERROR
|
||||
default n
|
||||
help
|
||||
Enable BLE Periodic Advertisement with Response(PAwR) feature
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
@@ -379,6 +379,25 @@ esp_err_t esp_ble_gattc_open(esp_gatt_if_t gattc_if, esp_bd_addr_t remote_bda, e
|
||||
esp_err_t esp_ble_gattc_aux_open(esp_gatt_if_t gattc_if, esp_bd_addr_t remote_bda, esp_ble_addr_type_t remote_addr_type, bool is_direct);
|
||||
#endif // #if (BLE_50_FEATURE_SUPPORT == TRUE)
|
||||
|
||||
#if (BT_BLE_FEAT_PAWR_EN == TRUE)
|
||||
/**
|
||||
* @brief Open an auxiliary connection with PAwR synced parameters
|
||||
*
|
||||
* @param[in] gattc_if GATT Client access interface
|
||||
* @param[in] pawr_conn_params PAwR connection parameters
|
||||
*
|
||||
* @note
|
||||
* 1. This function is used to establish a connection with a device that is synchronized to a PAwR advertiser.
|
||||
* 2. The function always triggers `ESP_GATTC_CONNECT_EVT` and `ESP_GATTC_OPEN_EVT`.
|
||||
*
|
||||
* @return
|
||||
* - ESP_OK: Success
|
||||
* - ESP_ERR_INVALID_ARG: Invalid argument
|
||||
* - ESP_FAIL: Failure
|
||||
*/
|
||||
esp_err_t esp_ble_gattc_aux_open_with_pawr_synced(esp_gatt_if_t gattc_if, esp_ble_gatt_pawr_conn_params_t *pawr_conn_params);
|
||||
#endif // #if (BT_BLE_FEAT_PAWR_EN == TRUE)
|
||||
|
||||
/**
|
||||
* @brief Close the virtual GATT Client connection
|
||||
*
|
||||
|
||||
@@ -1838,7 +1838,7 @@ void BTM_BleSetPaSubeventData(UINT8 adv_handle, UINT8 num_subevents_with_data, u
|
||||
tBTM_BLE_5_GAP_CB_PARAMS cb_params = {0};
|
||||
|
||||
if ((err = btsnd_hcic_ble_set_periodic_adv_subevt_data(adv_handle, num_subevents_with_data, (ble_subevent_params *)subevent_params)) != HCI_SUCCESS) {
|
||||
BTM_TRACE_ERROR("%s cmd err = 0x%x", __func__, err);
|
||||
BTM_TRACE_WARNING("%s cmd err = 0x%x", __func__, err);
|
||||
status = BTM_HCI_ERROR | err;
|
||||
}
|
||||
|
||||
|
||||
@@ -997,7 +997,9 @@ void gatt_data_process (tGATT_TCB *p_tcb, BT_HDR *p_buf)
|
||||
pseudo_op_code = op_code & (~GATT_WRITE_CMD_MASK);
|
||||
|
||||
if (pseudo_op_code < GATT_OP_CODE_MAX) {
|
||||
#if (GATTS_INCLUDED == TRUE) || (GATTC_INCLUDED == TRUE)
|
||||
GATT_TRACE_DEBUG("%s opcode=%x msg_len=%u", __func__, op_code, msg_len);
|
||||
#endif ///(GATTS_INCLUDED == TRUE) || (GATTC_INCLUDED == TRUE)
|
||||
if (op_code == GATT_SIGN_CMD_WRITE) {
|
||||
#if (SMP_INCLUDED == TRUE)
|
||||
gatt_verify_signature(p_tcb, p_buf);
|
||||
|
||||
@@ -1523,7 +1523,7 @@ typedef struct {
|
||||
UINT8 data_status;
|
||||
UINT8 data_len;
|
||||
UINT8 *data;
|
||||
} __attribute__((packed)) tBTM_BLE_PA_RSP_DATA_INFO;
|
||||
} tBTM_BLE_PA_RSP_DATA_INFO;
|
||||
|
||||
typedef struct {
|
||||
UINT8 adv_handle;
|
||||
|
||||
@@ -1295,7 +1295,7 @@ UINT8 btsnd_hcic_ble_set_ext_adv_params_v2(UINT8 adv_handle, UINT16 properties,
|
||||
#define HCIC_PARAM_SIZE_CREATE_CONFIG_PARAMS_LEN 28
|
||||
#define HCIC_PARAM_SIZE_REMOVE_CONFIG_PARAMS_LEN 3
|
||||
#define HCIC_PARAM_SIZE_SET_CHANNEL_CLASS_PARAMS_LEN 10
|
||||
#define HCIC_PARAM_SIZE_SET_PROCEDURE_PARAMS_LEN 21
|
||||
#define HCIC_PARAM_SIZE_SET_PROCEDURE_PARAMS_LEN 23
|
||||
#define HCIC_PARAM_SIZE_SET_PROCEDURE_ENABLE_PARAMS_LEN 4
|
||||
|
||||
UINT8 btsnd_hcic_ble_cs_read_local_supported_caps(void);
|
||||
|
||||
@@ -284,7 +284,7 @@ typedef struct {
|
||||
.ignore_wl_for_direct_adv = 0, \
|
||||
.enable_pcl = DEFAULT_BT_LE_POWER_CONTROL_ENABLED, \
|
||||
.csa2_select = DEFAULT_BT_LE_50_FEATURE_SUPPORT, \
|
||||
.enable_csr = 0, \
|
||||
.enable_csr = DEFAULT_BT_LE_SUBRATE_ENABLED, \
|
||||
.ble_aa_check = DEFAULT_BT_LE_CTRL_CHECK_CONNECT_IND_ACCESS_ADDRESS, \
|
||||
.ble_llcp_disc_flag = BT_LE_CTRL_LLCP_DISC_FLAG, \
|
||||
.scan_backoff_upperlimitmax = BT_CTRL_SCAN_BACKOFF_UPPERLIMITMAX, \
|
||||
|
||||
@@ -351,7 +351,7 @@ typedef struct {
|
||||
.ignore_wl_for_direct_adv = 0, \
|
||||
.enable_pcl = DEFAULT_BT_LE_POWER_CONTROL_ENABLED, \
|
||||
.csa2_select = DEFAULT_BT_LE_50_FEATURE_SUPPORT, \
|
||||
.enable_csr = 0, \
|
||||
.enable_csr = DEFAULT_BT_LE_SUBRATE_ENABLED, \
|
||||
.ble_aa_check = DEFAULT_BT_LE_CTRL_CHECK_CONNECT_IND_ACCESS_ADDRESS, \
|
||||
.ble_llcp_disc_flag = BT_LE_CTRL_LLCP_DISC_FLAG, \
|
||||
.scan_backoff_upperlimitmax = BT_CTRL_SCAN_BACKOFF_UPPERLIMITMAX, \
|
||||
|
||||
@@ -287,7 +287,7 @@ typedef struct {
|
||||
.ignore_wl_for_direct_adv = 0, \
|
||||
.enable_pcl = DEFAULT_BT_LE_POWER_CONTROL_ENABLED, \
|
||||
.csa2_select = DEFAULT_BT_LE_50_FEATURE_SUPPORT, \
|
||||
.enable_csr = 0, \
|
||||
.enable_csr = DEFAULT_BT_LE_SUBRATE_ENABLED, \
|
||||
.ble_aa_check = DEFAULT_BT_LE_CTRL_CHECK_CONNECT_IND_ACCESS_ADDRESS, \
|
||||
.ble_llcp_disc_flag = BT_LE_CTRL_LLCP_DISC_FLAG, \
|
||||
.scan_backoff_upperlimitmax = BT_CTRL_SCAN_BACKOFF_UPPERLIMITMAX, \
|
||||
|
||||
@@ -1667,6 +1667,10 @@ config SOC_BLE_CTE_SUPPORTED
|
||||
bool
|
||||
default y
|
||||
|
||||
config SOC_BLE_SUBRATE_SUPPORTED
|
||||
bool
|
||||
default y
|
||||
|
||||
config SOC_BLE_PERIODIC_ADV_WITH_RESPONSE
|
||||
bool
|
||||
default y
|
||||
|
||||
@@ -665,6 +665,7 @@
|
||||
#define SOC_BLE_MULTI_CONN_OPTIMIZATION (1) /*!< Support multiple connections optimization */
|
||||
#define SOC_BLE_PERIODIC_ADV_ENH_SUPPORTED (1) /*!< Support For BLE Periodic Adv Enhancements */
|
||||
#define SOC_BLE_CTE_SUPPORTED (1) /*!< Support Bluetooth LE Constant Tone Extension (CTE) */
|
||||
#define SOC_BLE_SUBRATE_SUPPORTED (1) /*!< Support Bluetooth LE Connection Subrating */
|
||||
#define SOC_BLE_PERIODIC_ADV_WITH_RESPONSE (1) /*!< Support Bluetooth LE Periodic Advertising with Response (PAwR) */
|
||||
|
||||
/*------------------------------------- PHY CAPS -------------------------------------*/
|
||||
|
||||
@@ -1431,6 +1431,10 @@ config SOC_BLE_MULTI_CONN_OPTIMIZATION
|
||||
bool
|
||||
default y
|
||||
|
||||
config SOC_BLE_SUBRATE_SUPPORTED
|
||||
bool
|
||||
default y
|
||||
|
||||
config SOC_BLE_PERIODIC_ADV_WITH_RESPONSE
|
||||
bool
|
||||
default y
|
||||
|
||||
@@ -583,6 +583,7 @@
|
||||
#define SOC_BLE_PERIODIC_ADV_ENH_SUPPORTED (1) /*!< Support For BLE Periodic Adv Enhancements */
|
||||
#define SOC_BLUFI_SUPPORTED (1) /*!< Support BLUFI */
|
||||
#define SOC_BLE_MULTI_CONN_OPTIMIZATION (1) /*!< Support multiple connections optimization */
|
||||
#define SOC_BLE_SUBRATE_SUPPORTED (1) /*!< Support Bluetooth LE Connection Subrating */
|
||||
#define SOC_BLE_PERIODIC_ADV_WITH_RESPONSE (1) /*!< Support Bluetooth LE Periodic Advertising with Response (PAwR) */
|
||||
|
||||
#define SOC_BLE_USE_WIFI_PWR_CLK_WORKAROUND (1)
|
||||
|
||||
@@ -1283,6 +1283,10 @@ config SOC_BLE_CTE_SUPPORTED
|
||||
bool
|
||||
default y
|
||||
|
||||
config SOC_BLE_SUBRATE_SUPPORTED
|
||||
bool
|
||||
default y
|
||||
|
||||
config SOC_BLE_PERIODIC_ADV_WITH_RESPONSE
|
||||
bool
|
||||
default y
|
||||
|
||||
@@ -519,6 +519,7 @@
|
||||
#define SOC_BLUFI_SUPPORTED (1) /*!< Support BLUFI */
|
||||
#define SOC_BLE_MULTI_CONN_OPTIMIZATION (1) /*!< Support multiple connections optimization */
|
||||
#define SOC_BLE_CTE_SUPPORTED (1) /*!< Support Bluetooth LE Constant Tone Extension (CTE) */
|
||||
#define SOC_BLE_SUBRATE_SUPPORTED (1) /*!< Support Bluetooth LE Connection Subrating */
|
||||
#define SOC_BLE_PERIODIC_ADV_WITH_RESPONSE (1) /*!< Support Bluetooth LE Periodic Advertising with Response (PAwR) */
|
||||
|
||||
/*------------------------------------- PHY CAPS -------------------------------------*/
|
||||
|
||||
@@ -1375,6 +1375,10 @@ config SOC_BLE_CTE_SUPPORTED
|
||||
bool
|
||||
default y
|
||||
|
||||
config SOC_BLE_SUBRATE_SUPPORTED
|
||||
bool
|
||||
default y
|
||||
|
||||
config SOC_BLE_PERIODIC_ADV_WITH_RESPONSE
|
||||
bool
|
||||
default y
|
||||
|
||||
@@ -577,6 +577,7 @@
|
||||
#define SOC_BLE_MULTI_CONN_OPTIMIZATION (1) /*!< Support multiple connections optimization */
|
||||
#define SOC_BLE_PERIODIC_ADV_ENH_SUPPORTED (1) /*!< Support For BLE Periodic Adv Enhancements */
|
||||
#define SOC_BLE_CTE_SUPPORTED (1) /*!< Support Bluetooth LE Constant Tone Extension (CTE) */
|
||||
#define SOC_BLE_SUBRATE_SUPPORTED (1) /*!< Support Bluetooth LE Connection Subrating */
|
||||
#define SOC_BLE_PERIODIC_ADV_WITH_RESPONSE (1) /*!< Support Bluetooth LE Periodic Advertising with Response (PAwR) */
|
||||
|
||||
/*------------------------------------- DEBUG CAPS -------------------------------------*/
|
||||
|
||||
@@ -45,6 +45,16 @@ examples/bluetooth/bluedroid/ble/ble_acl_latency/periph:
|
||||
temporary: true
|
||||
reason: only enable build jobs for tested targets
|
||||
|
||||
examples/bluetooth/bluedroid/ble/ble_enc_adv_data/enc_adv_data_cent:
|
||||
<<: *bt_default_depends
|
||||
disable:
|
||||
- if: SOC_BLE_SUPPORTED != 1
|
||||
|
||||
examples/bluetooth/bluedroid/ble/ble_enc_adv_data/enc_adv_data_prph:
|
||||
<<: *bt_default_depends
|
||||
disable:
|
||||
- if: SOC_BLE_SUPPORTED != 1
|
||||
|
||||
examples/bluetooth/bluedroid/ble/ble_hid_device_demo:
|
||||
<<: *bt_default_depends
|
||||
disable:
|
||||
@@ -76,6 +86,61 @@ examples/bluetooth/bluedroid/ble_50:
|
||||
depends_filepatterns:
|
||||
- examples/bluetooth/bluedroid/ble_50/pytest_ble50_test.py
|
||||
|
||||
examples/bluetooth/bluedroid/ble_50/ble_conn_subrating_central:
|
||||
<<: *bt_default_depends
|
||||
enable:
|
||||
- if: SOC_BLE_SUBRATE_SUPPORTED == 1
|
||||
|
||||
examples/bluetooth/bluedroid/ble_50/ble_conn_subrating_peripheral:
|
||||
<<: *bt_default_depends
|
||||
enable:
|
||||
- if: SOC_BLE_SUBRATE_SUPPORTED == 1
|
||||
|
||||
examples/bluetooth/bluedroid/ble_50/ble_connection_central_with_cte:
|
||||
<<: *bt_default_depends
|
||||
enable:
|
||||
- if: SOC_BLE_CTE_SUPPORTED == 1
|
||||
|
||||
examples/bluetooth/bluedroid/ble_50/ble_connection_peripheral_with_cte:
|
||||
<<: *bt_default_depends
|
||||
enable:
|
||||
- if: SOC_BLE_CTE_SUPPORTED == 1
|
||||
|
||||
examples/bluetooth/bluedroid/ble_50/ble_pawr_advertiser:
|
||||
<<: *bt_default_depends
|
||||
enable:
|
||||
- if: SOC_BLE_PERIODIC_ADV_WITH_RESPONSE == 1
|
||||
|
||||
examples/bluetooth/bluedroid/ble_50/ble_pawr_advertiser_conn:
|
||||
<<: *bt_default_depends
|
||||
enable:
|
||||
- if: SOC_BLE_PERIODIC_ADV_WITH_RESPONSE == 1
|
||||
|
||||
examples/bluetooth/bluedroid/ble_50/ble_pawr_synchronizer:
|
||||
<<: *bt_default_depends
|
||||
enable:
|
||||
- if: SOC_BLE_PERIODIC_ADV_WITH_RESPONSE == 1
|
||||
|
||||
examples/bluetooth/bluedroid/ble_50/ble_periodic_adv_with_cte:
|
||||
<<: *bt_default_depends
|
||||
enable:
|
||||
- if: SOC_BLE_CTE_SUPPORTED == 1
|
||||
|
||||
examples/bluetooth/bluedroid/ble_50/ble_periodic_sync_with_cte:
|
||||
<<: *bt_default_depends
|
||||
enable:
|
||||
- if: SOC_BLE_CTE_SUPPORTED == 1
|
||||
|
||||
examples/bluetooth/bluedroid/ble_50/ble_power_control_central:
|
||||
<<: *bt_default_depends
|
||||
enable:
|
||||
- if: SOC_BLE_POWER_CONTROL_SUPPORTED == 1
|
||||
|
||||
examples/bluetooth/bluedroid/ble_50/ble_power_control_peripheral:
|
||||
<<: *bt_default_depends
|
||||
enable:
|
||||
- if: SOC_BLE_POWER_CONTROL_SUPPORTED == 1
|
||||
|
||||
examples/bluetooth/bluedroid/classic_bt:
|
||||
<<: *bt_default_depends
|
||||
disable:
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
# The following lines of boilerplate have to be in your project's CMakeLists
|
||||
# in this exact order for cmake to work correctly
|
||||
cmake_minimum_required(VERSION 3.22)
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
# "Trim" the build. Include the minimal set of components, main, and anything it depends on.
|
||||
idf_build_set_property(MINIMAL_BUILD ON)
|
||||
project(ble_conn_subrating_central)
|
||||
@@ -0,0 +1,50 @@
|
||||
| Supported Targets | ESP32-C5 | ESP32-C6 | ESP32-C61 | ESP32-H2 |
|
||||
| ----------------- | -------- | -------- | --------- | -------- |
|
||||
|
||||
# BLE Connection Subrating Central Example
|
||||
|
||||
This example demonstrates how to use BLE Connection Subrating feature as a Central device using Bluedroid host API.
|
||||
|
||||
## What is BLE Connection Subrating?
|
||||
|
||||
BLE Connection Subrating is a BLE 5.0 feature that allows a Central or Peripheral to request a change to the subrating factor and/or other parameters applied to an existing connection. This can help optimize power consumption and connection intervals.
|
||||
|
||||
## How It Works
|
||||
|
||||
1. The Central device scans for BLE devices using extended scanning
|
||||
2. When it finds the target device (Peripheral), it connects to it
|
||||
3. After connection, the Central sets default subrate parameters
|
||||
4. The Central requests a subrate change to reduce power consumption
|
||||
5. The Central receives subrate change events when the subrating is updated
|
||||
|
||||
## API Usage
|
||||
|
||||
- `esp_ble_gap_set_default_subrate()` - Set default subrate parameters for future connections
|
||||
- `esp_ble_gap_subrate_request()` - Request subrate change on an existing connection
|
||||
- `ESP_GAP_BLE_SUBRATE_CHANGE_EVT` - Event received when subrate changes
|
||||
|
||||
## Configuration
|
||||
|
||||
The example requires BLE 5.0 features to be enabled:
|
||||
- `CONFIG_BT_BLE_50_FEATURES_SUPPORTED=y`
|
||||
- `CONFIG_BT_BLE_50_EXTEND_SCAN_EN=y`
|
||||
- `CONFIG_BT_BLE_FEAT_CONN_SUBRATING=y`
|
||||
|
||||
## Hardware Required
|
||||
|
||||
- One ESP32 development board (ESP32-C5, ESP32-C6, ESP32-H2, etc.) that supports BLE 5.0
|
||||
|
||||
## Build and Flash
|
||||
|
||||
```bash
|
||||
idf.py set-target esp32c5
|
||||
idf.py build flash monitor
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
1. Flash this example to one ESP32 device (Central)
|
||||
2. Flash the `ble_conn_subrating_peripheral` example to another ESP32 device (Peripheral)
|
||||
3. The Central will automatically scan, connect, and request subrate changes
|
||||
4. Monitor the serial output to see subrate change events
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
idf_component_register(SRCS "main.c"
|
||||
PRIV_REQUIRES bt nvs_flash
|
||||
INCLUDE_DIRS ".")
|
||||
@@ -0,0 +1,307 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Unlicense OR CC0-1.0
|
||||
*/
|
||||
|
||||
/****************************************************************************
|
||||
*
|
||||
* This demo showcases BLE Connection Subrating feature on Central side.
|
||||
* It scans for BLE devices and connects to a peripheral device that supports
|
||||
* Connection Subrating. After connection, it sets default subrate parameters
|
||||
* and requests a subrate change to reduce power consumption.
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include "nvs.h"
|
||||
#include "nvs_flash.h"
|
||||
|
||||
#include "esp_bt.h"
|
||||
#include "esp_gap_ble_api.h"
|
||||
#include "esp_gattc_api.h"
|
||||
#include "esp_gatt_defs.h"
|
||||
#include "esp_bt_main.h"
|
||||
#include "esp_gatt_common_api.h"
|
||||
#include "esp_log.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
|
||||
#define TAG "BLE_SUBRATING_CENTRAL"
|
||||
#define REMOTE_DEVICE_NAME "ESP_SUBRATE_PRH"
|
||||
#define PROFILE_NUM 1
|
||||
#define PROFILE_A_APP_ID 0
|
||||
|
||||
static bool connect = false;
|
||||
static uint16_t g_conn_handle = 0xFFFF;
|
||||
|
||||
static esp_ble_ext_scan_params_t ext_scan_params = {
|
||||
.own_addr_type = BLE_ADDR_TYPE_PUBLIC,
|
||||
.filter_policy = BLE_SCAN_FILTER_ALLOW_ALL,
|
||||
.scan_duplicate = BLE_SCAN_DUPLICATE_DISABLE,
|
||||
.cfg_mask = ESP_BLE_GAP_EXT_SCAN_CFG_UNCODE_MASK,
|
||||
.uncoded_cfg = {BLE_SCAN_TYPE_ACTIVE, 0x40, 0x40},
|
||||
.coded_cfg = {BLE_SCAN_TYPE_ACTIVE, 0x40, 0x40},
|
||||
};
|
||||
|
||||
// Connection parameters for different PHYs
|
||||
static const esp_ble_conn_params_t phy_1m_conn_params = {
|
||||
.scan_interval = 0x40,
|
||||
.scan_window = 0x40,
|
||||
.interval_min = 0x18, // 30ms
|
||||
.interval_max = 0x28, // 50ms
|
||||
.latency = 0,
|
||||
.supervision_timeout = 0x1F4, // 5s
|
||||
.min_ce_len = 0,
|
||||
.max_ce_len = 0,
|
||||
};
|
||||
|
||||
static const esp_ble_conn_params_t phy_2m_conn_params = {
|
||||
.scan_interval = 0x40,
|
||||
.scan_window = 0x40,
|
||||
.interval_min = 0x18,
|
||||
.interval_max = 0x28,
|
||||
.latency = 0,
|
||||
.supervision_timeout = 0x1F4,
|
||||
.min_ce_len = 0,
|
||||
.max_ce_len = 0,
|
||||
};
|
||||
|
||||
static const esp_ble_conn_params_t phy_coded_conn_params = {
|
||||
.scan_interval = 0x40,
|
||||
.scan_window = 0x40,
|
||||
.interval_min = 0x18,
|
||||
.interval_max = 0x28,
|
||||
.latency = 0,
|
||||
.supervision_timeout = 0x1F4,
|
||||
.min_ce_len = 0,
|
||||
.max_ce_len = 0,
|
||||
};
|
||||
|
||||
struct gattc_profile_inst {
|
||||
esp_gattc_cb_t gattc_cb;
|
||||
uint16_t gattc_if;
|
||||
uint16_t app_id;
|
||||
uint16_t conn_id;
|
||||
esp_bd_addr_t remote_bda;
|
||||
};
|
||||
|
||||
static struct gattc_profile_inst gl_profile_tab[PROFILE_NUM] = {
|
||||
[PROFILE_A_APP_ID] = {
|
||||
.gattc_cb = NULL,
|
||||
.gattc_if = ESP_GATT_IF_NONE,
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief GATT client event handler
|
||||
*/
|
||||
static void gattc_profile_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param)
|
||||
{
|
||||
if (param == NULL) {
|
||||
ESP_LOGE(TAG, "GATTC event handler received NULL param");
|
||||
return;
|
||||
}
|
||||
|
||||
esp_ble_gattc_cb_param_t *p_data = (esp_ble_gattc_cb_param_t *)param;
|
||||
|
||||
switch (event) {
|
||||
case ESP_GATTC_REG_EVT:
|
||||
ESP_LOGI(TAG, "GATT client register, status %d, app_id %d, gattc_if %d",
|
||||
p_data->reg.status, p_data->reg.app_id, gattc_if);
|
||||
gl_profile_tab[PROFILE_A_APP_ID].gattc_if = gattc_if;
|
||||
// Set default subrate parameters
|
||||
esp_ble_default_subrate_param_t default_subrate_params = {
|
||||
.subrate_min = 0x0001, // Minimum subrate factor
|
||||
.subrate_max = 0x000a, // Maximum subrate factor (10x slower)
|
||||
.max_latency = 0x0009, // Allow 9 intervals of latency
|
||||
.continuation_number = 0x0008, // Continuation number
|
||||
.supervision_timeout = 1000, // 10 seconds (1000 * 10ms)
|
||||
};
|
||||
esp_err_t ret = esp_ble_gap_set_default_subrate(&default_subrate_params);
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to set default subrate, error = 0x%x", ret);
|
||||
}
|
||||
break;
|
||||
case ESP_GATTC_CONNECT_EVT:
|
||||
ESP_LOGI(TAG, "Connected, conn_id %d, remote "ESP_BD_ADDR_STR"",
|
||||
p_data->connect.conn_id, ESP_BD_ADDR_HEX(p_data->connect.remote_bda));
|
||||
gl_profile_tab[PROFILE_A_APP_ID].conn_id = p_data->connect.conn_id;
|
||||
memcpy(gl_profile_tab[PROFILE_A_APP_ID].remote_bda, p_data->connect.remote_bda, sizeof(esp_bd_addr_t));
|
||||
g_conn_handle = p_data->connect.conn_handle;
|
||||
// Request subrate change after setting default parameters
|
||||
esp_ble_subrate_req_param_t subrate_req_params = {
|
||||
.conn_handle = g_conn_handle,
|
||||
.subrate_min = 0x0001, // Minimum subrate factor
|
||||
.subrate_max = 0x0003, // Maximum subrate factor (3x slower)
|
||||
.max_latency = 0x0001, // Allow 1 interval of latency
|
||||
.continuation_number = 0x0002, // Continuation number
|
||||
.supervision_timeout = 3000, // 30 seconds (3000 * 10ms)
|
||||
};
|
||||
esp_err_t subrate_ret = esp_ble_gap_subrate_request(&subrate_req_params);
|
||||
if (subrate_ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to request subrate, error = 0x%x", subrate_ret);
|
||||
} else {
|
||||
ESP_LOGI(TAG, "Subrate request sent successfully");
|
||||
}
|
||||
break;
|
||||
case ESP_GATTC_DISCONNECT_EVT:
|
||||
connect = false;
|
||||
g_conn_handle = 0xFFFF;
|
||||
ESP_LOGI(TAG, "Disconnected, remote "ESP_BD_ADDR_STR", reason 0x%02x",
|
||||
ESP_BD_ADDR_HEX(p_data->disconnect.remote_bda), p_data->disconnect.reason);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief GAP event handler
|
||||
*/
|
||||
static void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param)
|
||||
{
|
||||
if (param == NULL) {
|
||||
ESP_LOGE(TAG, "GAP event handler received NULL param");
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t *adv_name = NULL;
|
||||
uint8_t adv_name_len = 0;
|
||||
|
||||
switch (event) {
|
||||
case ESP_GAP_BLE_SET_EXT_SCAN_PARAMS_COMPLETE_EVT:
|
||||
ESP_LOGI(TAG, "Extended scan parameters set, starting scan...");
|
||||
esp_ble_gap_start_ext_scan(0, 0); // Scan indefinitely
|
||||
break;
|
||||
case ESP_GAP_BLE_EXT_SCAN_START_COMPLETE_EVT:
|
||||
if (param->ext_scan_start.status != ESP_BT_STATUS_SUCCESS) {
|
||||
ESP_LOGE(TAG, "Extended scan start failed, status %x", param->ext_scan_start.status);
|
||||
break;
|
||||
}
|
||||
ESP_LOGI(TAG, "Extended scan started successfully");
|
||||
break;
|
||||
case ESP_GAP_BLE_EXT_ADV_REPORT_EVT: {
|
||||
adv_name = esp_ble_resolve_adv_data_by_type(param->ext_adv_report.params.adv_data,
|
||||
param->ext_adv_report.params.adv_data_len,
|
||||
ESP_BLE_AD_TYPE_NAME_CMPL,
|
||||
&adv_name_len);
|
||||
if (adv_name != NULL && adv_name_len > 0) {
|
||||
ESP_LOGI(TAG, "Found device: "ESP_BD_ADDR_STR", name: %.*s",
|
||||
ESP_BD_ADDR_HEX(param->ext_adv_report.params.addr), adv_name_len, adv_name);
|
||||
|
||||
if (!connect && adv_name_len == strlen(REMOTE_DEVICE_NAME) &&
|
||||
strncmp((char *)adv_name, REMOTE_DEVICE_NAME, adv_name_len) == 0) {
|
||||
ESP_LOGI(TAG, "Target device found, connecting...");
|
||||
connect = true;
|
||||
esp_ble_gap_stop_ext_scan();
|
||||
esp_ble_gatt_creat_conn_params_t creat_conn_params = {0};
|
||||
memcpy(&creat_conn_params.remote_bda, param->ext_adv_report.params.addr, ESP_BD_ADDR_LEN);
|
||||
creat_conn_params.remote_addr_type = param->ext_adv_report.params.addr_type;
|
||||
creat_conn_params.own_addr_type = BLE_ADDR_TYPE_PUBLIC;
|
||||
creat_conn_params.is_direct = true;
|
||||
creat_conn_params.is_aux = true;
|
||||
creat_conn_params.phy_mask = ESP_BLE_PHY_1M_PREF_MASK | ESP_BLE_PHY_2M_PREF_MASK | ESP_BLE_PHY_CODED_PREF_MASK;
|
||||
creat_conn_params.phy_1m_conn_params = &phy_1m_conn_params;
|
||||
creat_conn_params.phy_2m_conn_params = &phy_2m_conn_params;
|
||||
creat_conn_params.phy_coded_conn_params = &phy_coded_conn_params;
|
||||
esp_ble_gattc_enh_open(gl_profile_tab[PROFILE_A_APP_ID].gattc_if, &creat_conn_params);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ESP_GAP_BLE_EXT_SCAN_STOP_COMPLETE_EVT:
|
||||
ESP_LOGI(TAG, "Extended scan stopped");
|
||||
break;
|
||||
case ESP_GAP_BLE_SET_DEFAULT_SUBRATE_COMPLETE_EVT:
|
||||
ESP_LOGI(TAG, "Set default subrate complete, status %d", param->set_default_subrate_evt.status);
|
||||
if (param->set_default_subrate_evt.status == ESP_BT_STATUS_SUCCESS) {
|
||||
esp_err_t scan_ret = esp_ble_gap_set_ext_scan_params(&ext_scan_params);
|
||||
if (scan_ret) {
|
||||
ESP_LOGE(TAG, "set ext scan params error, error code = %x", scan_ret);
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
case ESP_GAP_BLE_SUBRATE_REQUEST_COMPLETE_EVT:
|
||||
ESP_LOGI(TAG, "Subrate request complete, status %d", param->subrate_req_cmpl_evt.status);
|
||||
break;
|
||||
case ESP_GAP_BLE_SUBRATE_CHANGE_EVT:
|
||||
ESP_LOGI(TAG, "Connection subrating update, status %d, conn_hdl %d, subrate_factor %d latency %d, continuation_number %d, timeout %d",
|
||||
param->subrate_change_evt.status,
|
||||
param->subrate_change_evt.conn_handle,
|
||||
param->subrate_change_evt.subrate_factor,
|
||||
param->subrate_change_evt.peripheral_latency,
|
||||
param->subrate_change_evt.continuation_number,
|
||||
param->subrate_change_evt.supervision_timeout);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
esp_err_t ret;
|
||||
|
||||
// Initialize NVS
|
||||
ret = nvs_flash_init();
|
||||
if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
|
||||
ESP_ERROR_CHECK(nvs_flash_erase());
|
||||
ret = nvs_flash_init();
|
||||
}
|
||||
ESP_ERROR_CHECK(ret);
|
||||
|
||||
// Initialize Bluetooth Controller
|
||||
ESP_ERROR_CHECK(esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT));
|
||||
esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT();
|
||||
ret = esp_bt_controller_init(&bt_cfg);
|
||||
if (ret) {
|
||||
ESP_LOGE(TAG, "Bluetooth controller init failed, error = %x", ret);
|
||||
return;
|
||||
}
|
||||
|
||||
ret = esp_bt_controller_enable(ESP_BT_MODE_BLE);
|
||||
if (ret) {
|
||||
ESP_LOGE(TAG, "Bluetooth controller enable failed, error = %x", ret);
|
||||
return;
|
||||
}
|
||||
|
||||
// Initialize Bluedroid
|
||||
esp_bluedroid_config_t cfg = BT_BLUEDROID_INIT_CONFIG_DEFAULT();
|
||||
ret = esp_bluedroid_init_with_cfg(&cfg);
|
||||
if (ret) {
|
||||
ESP_LOGE(TAG, "Bluedroid init failed, error = %x", ret);
|
||||
return;
|
||||
}
|
||||
|
||||
ret = esp_bluedroid_enable();
|
||||
if (ret) {
|
||||
ESP_LOGE(TAG, "Bluedroid enable failed, error = %x", ret);
|
||||
return;
|
||||
}
|
||||
|
||||
// Register callbacks
|
||||
ret = esp_ble_gap_register_callback(gap_event_handler);
|
||||
if (ret) {
|
||||
ESP_LOGE(TAG, "GAP register callback failed, error = %x", ret);
|
||||
return;
|
||||
}
|
||||
|
||||
ret = esp_ble_gattc_register_callback(gattc_profile_event_handler);
|
||||
if (ret) {
|
||||
ESP_LOGE(TAG, "GATTC register callback failed, error = %x", ret);
|
||||
return;
|
||||
}
|
||||
|
||||
ret = esp_ble_gattc_app_register(PROFILE_A_APP_ID);
|
||||
if (ret) {
|
||||
ESP_LOGE(TAG, "GATTC app register failed, error = %x", ret);
|
||||
return;
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "BLE Connection Subrating Central example started");
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
# This file was generated using idf.py save-defconfig. It can be edited manually.
|
||||
# Espressif IoT Development Framework (ESP-IDF) Project Minimal Configuration
|
||||
#
|
||||
# BT config - Enable Bluetooth
|
||||
CONFIG_BT_ENABLED=y
|
||||
|
||||
# Enable Bluedroid host
|
||||
CONFIG_BT_BLUEDROID_ENABLED=y
|
||||
|
||||
# Enable BLE 5.0 features (required for Connection Subrating)
|
||||
CONFIG_BT_BLE_50_FEATURES_SUPPORTED=y
|
||||
|
||||
# Enable BLE Extended Scan (required for BLE 5.0 scanning)
|
||||
CONFIG_BT_BLE_50_EXTEND_SCAN_EN=y
|
||||
|
||||
# Enable BLE Connection Subrating feature
|
||||
CONFIG_BT_BLE_FEAT_CONN_SUBRATING=y
|
||||
|
||||
CONFIG_BT_GATTS_ENABLE=n
|
||||
|
||||
CONFIG_BT_BLE_SMP_ENABLE=n
|
||||
|
||||
CONFIG_BT_BLE_50_EXTEND_ADV_EN=n
|
||||
|
||||
CONFIG_BT_BLE_50_DTM_TEST_EN=n
|
||||
|
||||
CONFIG_BT_BLE_50_EXTEND_SYNC_EN=n
|
||||
@@ -0,0 +1,8 @@
|
||||
# The following lines of boilerplate have to be in your project's CMakeLists
|
||||
# in this exact order for cmake to work correctly
|
||||
cmake_minimum_required(VERSION 3.22)
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
# "Trim" the build. Include the minimal set of components, main, and anything it depends on.
|
||||
idf_build_set_property(MINIMAL_BUILD ON)
|
||||
project(ble_conn_subrating_peripheral)
|
||||
@@ -0,0 +1,48 @@
|
||||
| Supported Targets | ESP32-C5 | ESP32-C6 | ESP32-C61 | ESP32-H2 |
|
||||
| ----------------- | -------- | -------- | --------- | -------- |
|
||||
|
||||
# BLE Connection Subrating Peripheral Example
|
||||
|
||||
This example demonstrates how to use BLE Connection Subrating feature as a Peripheral device using Bluedroid host API.
|
||||
|
||||
## What is BLE Connection Subrating?
|
||||
|
||||
BLE Connection Subrating is a BLE 5.0 feature that allows a Central or Peripheral to request a change to the subrating factor and/or other parameters applied to an existing connection. This can help optimize power consumption and connection intervals.
|
||||
|
||||
## How It Works
|
||||
|
||||
1. The Peripheral device starts advertising
|
||||
2. When a Central connects, the Peripheral accepts the connection
|
||||
3. The Peripheral receives subrate change events when the Central requests subrate changes
|
||||
4. The Peripheral can also request subrate changes if needed
|
||||
|
||||
## API Usage
|
||||
|
||||
- `esp_ble_gap_set_default_subrate()` - Set default subrate parameters for future connections
|
||||
- `esp_ble_gap_subrate_request()` - Request subrate change on an existing connection
|
||||
- `ESP_GAP_BLE_SUBRATE_CHANGE_EVT` - Event received when subrate changes
|
||||
|
||||
## Configuration
|
||||
|
||||
The example requires BLE 5.0 features to be enabled:
|
||||
- `CONFIG_BT_BLE_50_FEATURES_SUPPORTED=y`
|
||||
- `CONFIG_BT_BLE_FEAT_CONN_SUBRATING=y`
|
||||
|
||||
## Hardware Required
|
||||
|
||||
- One ESP32 development board (ESP32-C5, ESP32-C6, ESP32-H2, etc.) that supports BLE 5.0
|
||||
|
||||
## Build and Flash
|
||||
|
||||
```bash
|
||||
idf.py set-target esp32c5
|
||||
idf.py build flash monitor
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
1. Flash this example to one ESP32 device (Peripheral)
|
||||
2. Flash the `ble_conn_subrating_central` example to another ESP32 device (Central)
|
||||
3. The Peripheral will start advertising and wait for connection
|
||||
4. Monitor the serial output to see subrate change events
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
idf_component_register(SRCS "main.c"
|
||||
PRIV_REQUIRES bt nvs_flash
|
||||
INCLUDE_DIRS ".")
|
||||
@@ -0,0 +1,376 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Unlicense OR CC0-1.0
|
||||
*/
|
||||
|
||||
/****************************************************************************
|
||||
*
|
||||
* This demo showcases BLE Connection Subrating feature on Peripheral side.
|
||||
* It advertises and waits for connection from a central device. After connection,
|
||||
* it accepts subrate requests from the central to reduce power consumption.
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "freertos/semphr.h"
|
||||
#include "esp_system.h"
|
||||
#include "esp_log.h"
|
||||
#include "nvs_flash.h"
|
||||
#include "esp_bt.h"
|
||||
#include "esp_gap_ble_api.h"
|
||||
#include "esp_gatts_api.h"
|
||||
#include "esp_bt_defs.h"
|
||||
#include "esp_bt_main.h"
|
||||
#include "esp_gatt_common_api.h"
|
||||
#include "subrating_service.h"
|
||||
|
||||
#define TAG "BLE_SUBRATING_PERIPHERAL"
|
||||
#define DEVICE_NAME "ESP_SUBRATE_PRH"
|
||||
|
||||
#define PROFILE_NUM 1
|
||||
#define PROFILE_A_APP_ID 0
|
||||
#define SVC_INST_ID 0
|
||||
|
||||
#define EXT_ADV_HANDLE 0
|
||||
#define NUM_EXT_ADV 1
|
||||
|
||||
#define SEM_WAIT_TIMEOUT_MS 5000
|
||||
|
||||
#define FUNC_SEND_WAIT_SEM(func, sem) do {\
|
||||
esp_err_t __err_rc = (func);\
|
||||
if (__err_rc != ESP_OK) { \
|
||||
ESP_LOGE(TAG, "%s, message send fail, error = %d", __func__, __err_rc); \
|
||||
} \
|
||||
if (xSemaphoreTake(sem, pdMS_TO_TICKS(SEM_WAIT_TIMEOUT_MS)) != pdTRUE) { \
|
||||
ESP_LOGE(TAG, "%s, semaphore timeout", __func__); \
|
||||
} \
|
||||
} while(0);
|
||||
|
||||
static uint16_t conn_handle = 0xFFFF;
|
||||
|
||||
uint16_t subrating_handle_table[SUBRATING_IDX_NB];
|
||||
|
||||
/* UUIDs */
|
||||
static const uint16_t primary_service_uuid = ESP_GATT_UUID_PRI_SERVICE;
|
||||
static const uint16_t character_declaration_uuid = ESP_GATT_UUID_CHAR_DECLARE;
|
||||
|
||||
/* Service UUID - must be a variable, not a macro, to take address */
|
||||
static const uint16_t subrating_service_uuid = BLE_UUID_SUBRATING_SERVICE_VAL;
|
||||
static const uint16_t subrating_info_char_uuid = BLE_UUID_SUBRATING_SERVICE_VAL;
|
||||
|
||||
/* Characteristic properties */
|
||||
static const uint8_t char_prop_read = ESP_GATT_CHAR_PROP_BIT_READ;
|
||||
|
||||
/* Subrating Info value */
|
||||
static uint8_t subrating_info_value[4] = {0x00, 0x00, 0x00, 0x00};
|
||||
|
||||
/* Full Database Description - Used to add attributes into the database */
|
||||
static const esp_gatts_attr_db_t gatt_db[SUBRATING_IDX_NB] = {
|
||||
// Service Declaration
|
||||
[IDX_SUBRATING_SVC] =
|
||||
{{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&primary_service_uuid, ESP_GATT_PERM_READ,
|
||||
sizeof(uint16_t), sizeof(subrating_service_uuid), (uint8_t *)&subrating_service_uuid}},
|
||||
|
||||
/* Subrating Info Characteristic Declaration */
|
||||
[IDX_SUBRATING_INFO_CHAR] =
|
||||
{{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&character_declaration_uuid, ESP_GATT_PERM_READ,
|
||||
sizeof(uint8_t), sizeof(uint8_t), (uint8_t *)&char_prop_read}},
|
||||
|
||||
/* Subrating Info Characteristic Value */
|
||||
[IDX_SUBRATING_INFO_VAL] =
|
||||
{{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&subrating_info_char_uuid, ESP_GATT_PERM_READ,
|
||||
sizeof(subrating_info_value), sizeof(subrating_info_value), (uint8_t *)subrating_info_value}},
|
||||
};
|
||||
|
||||
// Extended advertising data (includes device name)
|
||||
static uint8_t raw_ext_adv_data[] = {
|
||||
0x02, ESP_BLE_AD_TYPE_FLAG, 0x06,
|
||||
0x10, ESP_BLE_AD_TYPE_NAME_CMPL, 'E','S','P','_','S','U','B','R','A','T','E','_','P','R','H'
|
||||
};
|
||||
|
||||
static esp_ble_gap_ext_adv_params_t ext_adv_params = {
|
||||
.type = ESP_BLE_GAP_SET_EXT_ADV_PROP_CONNECTABLE,
|
||||
.interval_min = 0x20, // 32 * 0.625ms = 20ms
|
||||
.interval_max = 0x40, // 64 * 0.625ms = 40ms
|
||||
.channel_map = ADV_CHNL_ALL,
|
||||
.filter_policy = ADV_FILTER_ALLOW_SCAN_ANY_CON_ANY,
|
||||
.primary_phy = ESP_BLE_GAP_PHY_1M,
|
||||
.max_skip = 0,
|
||||
.secondary_phy = ESP_BLE_GAP_PHY_1M,
|
||||
.sid = 0,
|
||||
.scan_req_notif = false,
|
||||
.own_addr_type = BLE_ADDR_TYPE_PUBLIC,
|
||||
.tx_power = EXT_ADV_TX_PWR_NO_PREFERENCE,
|
||||
};
|
||||
|
||||
static esp_ble_gap_ext_adv_t ext_adv[1] = {
|
||||
[0] = {EXT_ADV_HANDLE, 0, 0},
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief GAP event handler
|
||||
*/
|
||||
static void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param)
|
||||
{
|
||||
if (param == NULL) {
|
||||
ESP_LOGE(TAG, "GAP event handler received NULL param");
|
||||
return;
|
||||
}
|
||||
|
||||
switch (event) {
|
||||
case ESP_GAP_BLE_EXT_ADV_SET_PARAMS_COMPLETE_EVT:
|
||||
ESP_LOGI(TAG, "Extended advertising params set, status %d, instance %d",
|
||||
param->ext_adv_set_params.status, param->ext_adv_set_params.instance);
|
||||
if (param->ext_adv_set_params.status == ESP_BT_STATUS_SUCCESS) {
|
||||
// Set extended advertising data
|
||||
esp_ble_gap_config_ext_adv_data_raw(EXT_ADV_HANDLE, sizeof(raw_ext_adv_data), raw_ext_adv_data);
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Failed to set extended advertising params, status %d", param->ext_adv_set_params.status);
|
||||
}
|
||||
break;
|
||||
case ESP_GAP_BLE_EXT_ADV_DATA_SET_COMPLETE_EVT:
|
||||
ESP_LOGI(TAG, "Extended advertising data set, status %d, instance %d",
|
||||
param->ext_adv_data_set.status, param->ext_adv_data_set.instance);
|
||||
if (param->ext_adv_data_set.status == ESP_BT_STATUS_SUCCESS) {
|
||||
// Start extended advertising
|
||||
esp_ble_gap_ext_adv_start(NUM_EXT_ADV, ext_adv);
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Failed to set extended advertising data, status %d", param->ext_adv_data_set.status);
|
||||
}
|
||||
break;
|
||||
case ESP_GAP_BLE_EXT_ADV_START_COMPLETE_EVT:
|
||||
if (param->ext_adv_start.status != ESP_BT_STATUS_SUCCESS) {
|
||||
ESP_LOGE(TAG, "Extended advertising start failed, status %d", param->ext_adv_start.status);
|
||||
break;
|
||||
}
|
||||
ESP_LOGI(TAG, "Extended advertising start successfully, instance num %d",
|
||||
param->ext_adv_start.instance_num);
|
||||
break;
|
||||
case ESP_GAP_BLE_EXT_ADV_STOP_COMPLETE_EVT:
|
||||
if (param->ext_adv_stop.status != ESP_BT_STATUS_SUCCESS) {
|
||||
ESP_LOGE(TAG, "Extended advertising stop failed, status %d", param->ext_adv_stop.status);
|
||||
break;
|
||||
}
|
||||
ESP_LOGI(TAG, "Extended advertising stop successfully");
|
||||
break;
|
||||
case ESP_GAP_BLE_UPDATE_CONN_PARAMS_EVT:
|
||||
ESP_LOGI(TAG, "Connection parameters updated: min_int=%d, max_int=%d, conn_int=%d, latency=%d, timeout=%d",
|
||||
param->update_conn_params.min_int,
|
||||
param->update_conn_params.max_int,
|
||||
param->update_conn_params.conn_int,
|
||||
param->update_conn_params.latency,
|
||||
param->update_conn_params.timeout);
|
||||
break;
|
||||
case ESP_GAP_BLE_SUBRATE_CHANGE_EVT:
|
||||
ESP_LOGI(TAG, "Connection subrating update, status %d, conn_hdl %d, subrate_factor %d latency %d, continuation_number %d, timeout %d",
|
||||
param->subrate_change_evt.status,
|
||||
param->subrate_change_evt.conn_handle,
|
||||
param->subrate_change_evt.subrate_factor,
|
||||
param->subrate_change_evt.peripheral_latency,
|
||||
param->subrate_change_evt.continuation_number,
|
||||
param->subrate_change_evt.supervision_timeout);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
struct gatts_profile_inst {
|
||||
esp_gatts_cb_t gatts_cb;
|
||||
uint16_t gatts_if;
|
||||
uint16_t app_id;
|
||||
uint16_t conn_id;
|
||||
};
|
||||
|
||||
static void gatts_profile_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param);
|
||||
|
||||
static struct gatts_profile_inst gl_profile_tab[PROFILE_NUM] = {
|
||||
[PROFILE_A_APP_ID] = {
|
||||
.gatts_cb = gatts_profile_event_handler,
|
||||
.gatts_if = ESP_GATT_IF_NONE,
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief GATTS profile event handler
|
||||
*/
|
||||
static void gatts_profile_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param)
|
||||
{
|
||||
if (param == NULL) {
|
||||
ESP_LOGE(TAG, "GATTS event handler received NULL param");
|
||||
return;
|
||||
}
|
||||
|
||||
switch (event) {
|
||||
case ESP_GATTS_REG_EVT: {
|
||||
ESP_LOGI(TAG, "GATT server register, status %d, app_id %d, gatts_if %d",
|
||||
param->reg.status, param->reg.app_id, gatts_if);
|
||||
|
||||
esp_ble_gap_set_device_name(DEVICE_NAME);\
|
||||
|
||||
// Set extended advertising parameters
|
||||
esp_ble_gap_ext_adv_set_params(EXT_ADV_HANDLE, &ext_adv_params);
|
||||
|
||||
// Create attribute table
|
||||
esp_err_t create_attr_ret = esp_ble_gatts_create_attr_tab(gatt_db, gatts_if, SUBRATING_IDX_NB, SVC_INST_ID);
|
||||
if (create_attr_ret) {
|
||||
ESP_LOGE(TAG, "create attr table failed, error code = %x", create_attr_ret);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ESP_GATTS_CREAT_ATTR_TAB_EVT: {
|
||||
if (param->add_attr_tab.status != ESP_GATT_OK) {
|
||||
ESP_LOGE(TAG, "create attribute table failed, error code=0x%x", param->add_attr_tab.status);
|
||||
} else if (param->add_attr_tab.num_handle != SUBRATING_IDX_NB) {
|
||||
ESP_LOGE(TAG, "create attribute table abnormally, num_handle (%d) doesn't equal to SUBRATING_IDX_NB(%d)",
|
||||
param->add_attr_tab.num_handle, SUBRATING_IDX_NB);
|
||||
} else {
|
||||
ESP_LOGI(TAG, "create attribute table successfully, the number handle = %d", param->add_attr_tab.num_handle);
|
||||
// Validate handles pointer before memcpy
|
||||
if (param->add_attr_tab.handles) {
|
||||
memcpy(subrating_handle_table, param->add_attr_tab.handles, sizeof(subrating_handle_table));
|
||||
esp_ble_gatts_start_service(subrating_handle_table[IDX_SUBRATING_SVC]);
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Invalid handles pointer");
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ESP_GATTS_START_EVT:
|
||||
ESP_LOGI(TAG, "Service start, status %d, service_handle %d",
|
||||
param->start.status, param->start.service_handle);
|
||||
break;
|
||||
case ESP_GATTS_CONNECT_EVT: {
|
||||
ESP_LOGI(TAG, "Connected, conn_id %u, remote "ESP_BD_ADDR_STR"",
|
||||
param->connect.conn_id, ESP_BD_ADDR_HEX(param->connect.remote_bda));
|
||||
gl_profile_tab[PROFILE_A_APP_ID].conn_id = param->connect.conn_id;
|
||||
conn_handle = param->connect.conn_id;
|
||||
|
||||
esp_ble_conn_update_params_t conn_params = {0};
|
||||
memcpy(conn_params.bda, param->connect.remote_bda, sizeof(esp_bd_addr_t));
|
||||
conn_params.latency = 0;
|
||||
conn_params.max_int = 0x20;
|
||||
conn_params.min_int = 0x10;
|
||||
conn_params.timeout = 400;
|
||||
esp_ble_gap_update_conn_params(&conn_params);
|
||||
|
||||
// Stop extended advertising
|
||||
uint8_t adv_handle = EXT_ADV_HANDLE;
|
||||
esp_ble_gap_ext_adv_stop(1, &adv_handle);
|
||||
break;
|
||||
}
|
||||
case ESP_GATTS_DISCONNECT_EVT:
|
||||
ESP_LOGI(TAG, "Disconnected, remote "ESP_BD_ADDR_STR", reason 0x%02x",
|
||||
ESP_BD_ADDR_HEX(param->disconnect.remote_bda), param->disconnect.reason);
|
||||
conn_handle = 0xFFFF;
|
||||
// Restart extended advertising
|
||||
esp_ble_gap_ext_adv_start(NUM_EXT_ADV, ext_adv);
|
||||
break;
|
||||
case ESP_GATTS_READ_EVT:
|
||||
ESP_LOGI(TAG, "Read event, handle %d", param->read.handle);
|
||||
// Auto response is enabled, so no need to send response manually
|
||||
break;
|
||||
case ESP_GATTS_WRITE_EVT:
|
||||
ESP_LOGI(TAG, "Write event, handle %d, len %d", param->write.handle, param->write.len);
|
||||
// Auto response is enabled, so no need to send response manually
|
||||
break;
|
||||
case ESP_GATTS_MTU_EVT:
|
||||
ESP_LOGI(TAG, "MTU exchange, MTU %d", param->mtu.mtu);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void esp_gatts_cb(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param)
|
||||
{
|
||||
if (event == ESP_GATTS_REG_EVT) {
|
||||
if (param->reg.status == ESP_GATT_OK) {
|
||||
gl_profile_tab[param->reg.app_id].gatts_if = gatts_if;
|
||||
} else {
|
||||
ESP_LOGI(TAG, "reg app failed, app_id %04x, status %d",
|
||||
param->reg.app_id, param->reg.status);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
do {
|
||||
int idx;
|
||||
for (idx = 0; idx < PROFILE_NUM; idx++) {
|
||||
if (gatts_if == ESP_GATT_IF_NONE ||
|
||||
gatts_if == gl_profile_tab[idx].gatts_if) {
|
||||
if (gl_profile_tab[idx].gatts_cb) {
|
||||
gl_profile_tab[idx].gatts_cb(event, gatts_if, param);
|
||||
}
|
||||
}
|
||||
}
|
||||
} while (0);
|
||||
}
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
esp_err_t ret;
|
||||
|
||||
// Initialize NVS
|
||||
ret = nvs_flash_init();
|
||||
if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
|
||||
ESP_ERROR_CHECK(nvs_flash_erase());
|
||||
ret = nvs_flash_init();
|
||||
}
|
||||
ESP_ERROR_CHECK(ret);
|
||||
|
||||
// Initialize Bluetooth Controller
|
||||
ESP_ERROR_CHECK(esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT));
|
||||
esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT();
|
||||
ret = esp_bt_controller_init(&bt_cfg);
|
||||
if (ret) {
|
||||
ESP_LOGE(TAG, "Bluetooth controller init failed, error = %x", ret);
|
||||
return;
|
||||
}
|
||||
|
||||
ret = esp_bt_controller_enable(ESP_BT_MODE_BLE);
|
||||
if (ret) {
|
||||
ESP_LOGE(TAG, "Bluetooth controller enable failed, error = %x", ret);
|
||||
return;
|
||||
}
|
||||
|
||||
// Initialize Bluedroid
|
||||
esp_bluedroid_config_t cfg = BT_BLUEDROID_INIT_CONFIG_DEFAULT();
|
||||
ret = esp_bluedroid_init_with_cfg(&cfg);
|
||||
if (ret) {
|
||||
ESP_LOGE(TAG, "Bluedroid init failed, error = %x", ret);
|
||||
return;
|
||||
}
|
||||
|
||||
ret = esp_bluedroid_enable();
|
||||
if (ret) {
|
||||
ESP_LOGE(TAG, "Bluedroid enable failed, error = %x", ret);
|
||||
return;
|
||||
}
|
||||
|
||||
// Register callbacks
|
||||
ret = esp_ble_gap_register_callback(gap_event_handler);
|
||||
if (ret) {
|
||||
ESP_LOGE(TAG, "GAP register callback failed, error = %x", ret);
|
||||
return;
|
||||
}
|
||||
|
||||
ret = esp_ble_gatts_register_callback(esp_gatts_cb);
|
||||
if (ret) {
|
||||
ESP_LOGE(TAG, "gatts register error, error code = %x", ret);
|
||||
return;
|
||||
}
|
||||
|
||||
ret = esp_ble_gatts_app_register(PROFILE_A_APP_ID);
|
||||
if (ret) {
|
||||
ESP_LOGE(TAG, "gatts app register error, error code = %x", ret);
|
||||
return;
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "BLE Connection Subrating Peripheral example started");
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
|
||||
* SPDX-License-Identifier: Unlicense OR CC0-1.0
|
||||
*/
|
||||
|
||||
#ifndef SUBRATING_SERVICE_H
|
||||
#define SUBRATING_SERVICE_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
/* Subrating Service UUIDs */
|
||||
#define BLE_UUID_SUBRATING_SERVICE_VAL 0xFF00
|
||||
|
||||
/* Attributes State Machine */
|
||||
enum {
|
||||
IDX_SUBRATING_SVC,
|
||||
|
||||
/* Subrating Info Characteristic */
|
||||
IDX_SUBRATING_INFO_CHAR,
|
||||
IDX_SUBRATING_INFO_VAL,
|
||||
|
||||
SUBRATING_IDX_NB,
|
||||
};
|
||||
|
||||
#endif /* SUBRATING_SERVICE_H */
|
||||
@@ -0,0 +1,27 @@
|
||||
# This file was generated using idf.py save-defconfig. It can be edited manually.
|
||||
# Espressif IoT Development Framework (ESP-IDF) Project Minimal Configuration
|
||||
#
|
||||
# BT config - Enable Bluetooth
|
||||
CONFIG_BT_ENABLED=y
|
||||
|
||||
# Enable Bluedroid host
|
||||
CONFIG_BT_BLUEDROID_ENABLED=y
|
||||
|
||||
# Enable BLE 5.0 features (required for Connection Subrating)
|
||||
CONFIG_BT_BLE_50_FEATURES_SUPPORTED=y
|
||||
|
||||
# Enable BLE Extended Advertising (required for BLE 5.0 advertising)
|
||||
CONFIG_BT_BLE_50_EXTEND_ADV_EN=y
|
||||
|
||||
# Enable BLE Connection Subrating feature
|
||||
CONFIG_BT_BLE_FEAT_CONN_SUBRATING=y
|
||||
|
||||
CONFIG_BT_GATTC_ENABLE=n
|
||||
|
||||
CONFIG_BT_BLE_SMP_ENABLE=n
|
||||
|
||||
CONFIG_BT_BLE_50_PERIODIC_ADV_EN=n
|
||||
|
||||
CONFIG_BT_BLE_50_EXTEND_SCAN_EN=n
|
||||
|
||||
CONFIG_BT_BLE_50_DTM_TEST_EN=n
|
||||
@@ -0,0 +1,8 @@
|
||||
# The following lines of boilerplate have to be in your project's CMakeLists
|
||||
# in this exact order for cmake to work correctly
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
# "Trim" the build. Include the minimal set of components, main, and anything it depends on.
|
||||
idf_build_set_property(MINIMAL_BUILD ON)
|
||||
project(connection_central_with_cte_demo)
|
||||
@@ -0,0 +1,75 @@
|
||||
| Supported Targets | ESP32-C5 | ESP32-C61 | ESP32-H2 |
|
||||
| ----------------- | -------- | --------- | -------- |
|
||||
|
||||
# ESP-IDF BLE Connection Central with CTE Example
|
||||
|
||||
This example demonstrates how to use BLE Constant Tone Extension (CTE) feature as a Central device using Bluedroid host API.
|
||||
|
||||
## What is BLE CTE?
|
||||
|
||||
BLE Constant Tone Extension (CTE) is a BLE 5.0 feature that enables Direction Finding (AoA/AoD) functionality. CTE allows devices to determine the direction of a Bluetooth signal by analyzing the phase of the received signal.
|
||||
|
||||
## How It Works
|
||||
|
||||
1. The Central device scans for BLE devices using extended scanning
|
||||
2. When it finds the target device (Peripheral), it connects to it
|
||||
3. After connection, the Central configures CTE receive parameters
|
||||
4. The Central enables CTE request to receive CTE from the Peripheral
|
||||
5. The Central receives IQ samples for direction finding
|
||||
|
||||
## API Usage
|
||||
|
||||
- `esp_ble_cte_register_callback()` - Register CTE event callback
|
||||
- `esp_ble_cte_set_connection_receive_params()` - Set CTE receive parameters for connection
|
||||
- `esp_ble_cte_connection_cte_request_enable()` - Enable CTE request on connection
|
||||
- `ESP_BLE_CTE_CONN_IQ_REPORT_EVT` - Event received when IQ samples are available
|
||||
|
||||
## Configuration
|
||||
|
||||
The example requires BLE 5.0 CTE features to be enabled:
|
||||
- `CONFIG_BT_BLE_50_FEATURES_SUPPORTED=y`
|
||||
- `CONFIG_BT_BLE_FEAT_CTE_EN=y`
|
||||
- `CONFIG_BT_LE_CTE_FEATURE_ENABLED=y`
|
||||
|
||||
## Hardware Required
|
||||
|
||||
* A development board with ESP32-C5, ESP32-C61, or ESP32-H2 SoC that supports BLE 5.0 CTE
|
||||
* A USB cable for Power supply and programming
|
||||
|
||||
See [Development Boards](https://www.espressif.com/en/products/devkits) for more information about it.
|
||||
|
||||
## How to Use Example
|
||||
|
||||
Before project configuration and build, be sure to set the correct chip target using:
|
||||
|
||||
```bash
|
||||
idf.py set-target esp32c5 # or esp32c61 or esp32h2
|
||||
```
|
||||
|
||||
To test this demo, you can run the [ble_connection_peripheral_with_cte](../ble_connection_peripheral_with_cte) example on another device, which starts advertising and can be connected to this demo automatically.
|
||||
|
||||
### 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
|
||||
|
||||
The example will output CTE-related events including:
|
||||
- CTE parameter configuration completion
|
||||
- CTE request enable completion
|
||||
- IQ sample reports for direction finding
|
||||
|
||||
```
|
||||
I (xxx) CTE_TEST: CTE set connection receive params, status 0
|
||||
I (xxx) CTE_TEST: CTE set connection request enable, status 0
|
||||
I (xxx) CTE_TEST: CTE connection IQ report received
|
||||
...
|
||||
```
|
||||
|
||||
## 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 "connection_central_with_cte.c"
|
||||
PRIV_REQUIRES bt nvs_flash
|
||||
INCLUDE_DIRS ".")
|
||||
@@ -0,0 +1,611 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Unlicense OR CC0-1.0
|
||||
*/
|
||||
|
||||
|
||||
|
||||
/****************************************************************************
|
||||
*
|
||||
* This file is for gatt_security_client demo. It can scan ble device, connect one device that needs to be encrypted.
|
||||
* run gatt_security_server demo, the gatt_security_client demo will automatically connect the gatt_security_server,
|
||||
* then paring and bonding.
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <inttypes.h>
|
||||
#include "nvs.h"
|
||||
#include "nvs_flash.h"
|
||||
|
||||
#include "esp_bt.h"
|
||||
#include "esp_gap_ble_api.h"
|
||||
#include "esp_gattc_api.h"
|
||||
#include "esp_gatt_defs.h"
|
||||
#include "esp_bt_main.h"
|
||||
#include "esp_gatt_common_api.h"
|
||||
#include "esp_log.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "esp_ble_cte_api.h"
|
||||
|
||||
#define LOG_TAG "CTE_TEST"
|
||||
#define REMOTE_SERVICE_UUID 0x00FF
|
||||
#define REMOTE_NOTIFY_UUID 0xFF01
|
||||
#define EXT_SCAN_DURATION 0
|
||||
#define EXT_SCAN_PERIOD 0
|
||||
|
||||
///Declare static functions
|
||||
static void esp_gap_cb(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param);
|
||||
static void esp_gattc_cb(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param);
|
||||
static void gattc_profile_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param);
|
||||
|
||||
|
||||
static esp_bt_uuid_t remote_filter_service_uuid = {
|
||||
.len = ESP_UUID_LEN_16,
|
||||
.uuid = {.uuid16 = REMOTE_SERVICE_UUID,},
|
||||
};
|
||||
|
||||
static bool connect = false;
|
||||
static bool get_service = false;
|
||||
static char remote_device_name[ESP_BLE_ADV_NAME_LEN_MAX] = "ESP_CON_CTE_TEST";
|
||||
|
||||
static esp_ble_ext_scan_params_t ext_scan_params = {
|
||||
.own_addr_type = BLE_ADDR_TYPE_PUBLIC,
|
||||
.filter_policy = BLE_SCAN_FILTER_ALLOW_ALL,
|
||||
.scan_duplicate = BLE_SCAN_DUPLICATE_DISABLE,
|
||||
.cfg_mask = ESP_BLE_GAP_EXT_SCAN_CFG_UNCODE_MASK | ESP_BLE_GAP_EXT_SCAN_CFG_CODE_MASK,
|
||||
.uncoded_cfg = {BLE_SCAN_TYPE_ACTIVE, 40, 40},
|
||||
.coded_cfg = {BLE_SCAN_TYPE_ACTIVE, 40, 40},
|
||||
};
|
||||
|
||||
const esp_ble_conn_params_t phy_1m_conn_params = {
|
||||
.scan_interval = 0x40,
|
||||
.scan_window = 0x40,
|
||||
.interval_min = 320,
|
||||
.interval_max = 320,
|
||||
.latency = 0,
|
||||
.supervision_timeout = 600,
|
||||
.min_ce_len = 0,
|
||||
.max_ce_len = 0,
|
||||
};
|
||||
const esp_ble_conn_params_t phy_2m_conn_params = {
|
||||
.scan_interval = 0x40,
|
||||
.scan_window = 0x40,
|
||||
.interval_min = 320,
|
||||
.interval_max = 320,
|
||||
.latency = 0,
|
||||
.supervision_timeout = 600,
|
||||
.min_ce_len = 0,
|
||||
.max_ce_len = 0,
|
||||
};
|
||||
const esp_ble_conn_params_t phy_coded_conn_params = {
|
||||
.scan_interval = 0x40,
|
||||
.scan_window = 0x40,
|
||||
.interval_min = 320, // 306-> 362Kbps
|
||||
.interval_max = 320,
|
||||
.latency = 0,
|
||||
.supervision_timeout = 600,
|
||||
.min_ce_len = 0,
|
||||
.max_ce_len = 0,
|
||||
};
|
||||
|
||||
static uint8_t antenna_ids[2] = {0x00, 0x01};
|
||||
esp_ble_cte_recv_params_params_t cte_recv_params = {
|
||||
.conn_handle = 0xff,
|
||||
.sampling_en = ESP_BLE_CTE_SAMPLING_ENABLE,
|
||||
.slot_dur = ESP_BLE_CTE_SLOT_DURATION_2US,
|
||||
.switching_pattern_len = sizeof(antenna_ids),
|
||||
.antenna_ids = &antenna_ids[0],
|
||||
};
|
||||
|
||||
static esp_ble_cte_req_en_params_t cte_conn_req_en = {
|
||||
.conn_handle = 0xff,
|
||||
.enable = ESP_BLE_CTE_SAMPLING_ENABLE,
|
||||
.cte_req_interval = 0x05,
|
||||
.req_cte_len = ESP_BLE_CTE_MAX_REQUESTED_CTE_LENGTH,
|
||||
.req_cte_Type = ESP_BLE_CTE_TYPE_AOA,
|
||||
};
|
||||
|
||||
uint16_t cur_conn_hdl = 0xff;
|
||||
|
||||
#define PROFILE_NUM 1
|
||||
#define PROFILE_A_APP_ID 0
|
||||
#define INVALID_HANDLE 0
|
||||
|
||||
struct gattc_profile_inst {
|
||||
esp_gattc_cb_t gattc_cb;
|
||||
uint16_t gattc_if;
|
||||
uint16_t app_id;
|
||||
uint16_t conn_id;
|
||||
uint16_t service_start_handle;
|
||||
uint16_t service_end_handle;
|
||||
uint16_t notify_char_handle;
|
||||
esp_bd_addr_t remote_bda;
|
||||
};
|
||||
|
||||
/* One gatt-based profile one app_id and one gattc_if, this array will store the gattc_if returned by ESP_GATTS_REG_EVT */
|
||||
static struct gattc_profile_inst gl_profile_tab[PROFILE_NUM] = {
|
||||
[PROFILE_A_APP_ID] = {
|
||||
.gattc_cb = gattc_profile_event_handler,
|
||||
.gattc_if = ESP_GATT_IF_NONE, /* Not get the gatt_if, so initial is ESP_GATT_IF_NONE */
|
||||
},
|
||||
};
|
||||
|
||||
static const char *esp_key_type_to_str(esp_ble_key_type_t key_type)
|
||||
{
|
||||
const char *key_str = NULL;
|
||||
switch(key_type) {
|
||||
case ESP_LE_KEY_NONE:
|
||||
key_str = "ESP_LE_KEY_NONE";
|
||||
break;
|
||||
case ESP_LE_KEY_PENC:
|
||||
key_str = "ESP_LE_KEY_PENC";
|
||||
break;
|
||||
case ESP_LE_KEY_PID:
|
||||
key_str = "ESP_LE_KEY_PID";
|
||||
break;
|
||||
case ESP_LE_KEY_PCSRK:
|
||||
key_str = "ESP_LE_KEY_PCSRK";
|
||||
break;
|
||||
case ESP_LE_KEY_PLK:
|
||||
key_str = "ESP_LE_KEY_PLK";
|
||||
break;
|
||||
case ESP_LE_KEY_LLK:
|
||||
key_str = "ESP_LE_KEY_LLK";
|
||||
break;
|
||||
case ESP_LE_KEY_LENC:
|
||||
key_str = "ESP_LE_KEY_LENC";
|
||||
break;
|
||||
case ESP_LE_KEY_LID:
|
||||
key_str = "ESP_LE_KEY_LID";
|
||||
break;
|
||||
case ESP_LE_KEY_LCSRK:
|
||||
key_str = "ESP_LE_KEY_LCSRK";
|
||||
break;
|
||||
default:
|
||||
key_str = "INVALID BLE KEY TYPE";
|
||||
break;
|
||||
|
||||
}
|
||||
return key_str;
|
||||
}
|
||||
|
||||
static char *esp_auth_req_to_str(esp_ble_auth_req_t auth_req)
|
||||
{
|
||||
char *auth_str = NULL;
|
||||
switch(auth_req) {
|
||||
case ESP_LE_AUTH_NO_BOND:
|
||||
auth_str = "ESP_LE_AUTH_NO_BOND";
|
||||
break;
|
||||
case ESP_LE_AUTH_BOND:
|
||||
auth_str = "ESP_LE_AUTH_BOND";
|
||||
break;
|
||||
case ESP_LE_AUTH_REQ_MITM:
|
||||
auth_str = "ESP_LE_AUTH_REQ_MITM";
|
||||
break;
|
||||
case ESP_LE_AUTH_REQ_BOND_MITM:
|
||||
auth_str = "ESP_LE_AUTH_REQ_BOND_MITM";
|
||||
break;
|
||||
case ESP_LE_AUTH_REQ_SC_ONLY:
|
||||
auth_str = "ESP_LE_AUTH_REQ_SC_ONLY";
|
||||
break;
|
||||
case ESP_LE_AUTH_REQ_SC_BOND:
|
||||
auth_str = "ESP_LE_AUTH_REQ_SC_BOND";
|
||||
break;
|
||||
case ESP_LE_AUTH_REQ_SC_MITM:
|
||||
auth_str = "ESP_LE_AUTH_REQ_SC_MITM";
|
||||
break;
|
||||
case ESP_LE_AUTH_REQ_SC_MITM_BOND:
|
||||
auth_str = "ESP_LE_AUTH_REQ_SC_MITM_BOND";
|
||||
break;
|
||||
default:
|
||||
auth_str = "INVALID BLE AUTH REQ";
|
||||
break;
|
||||
}
|
||||
|
||||
return auth_str;
|
||||
}
|
||||
|
||||
static void gattc_profile_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param)
|
||||
{
|
||||
esp_ble_gattc_cb_param_t *p_data = (esp_ble_gattc_cb_param_t *)param;
|
||||
|
||||
switch (event) {
|
||||
case ESP_GATTC_REG_EVT:
|
||||
ESP_LOGI(LOG_TAG, "GATT client register, status %u", p_data->reg.status);
|
||||
esp_ble_gap_config_local_privacy(true);
|
||||
break;
|
||||
case ESP_GATTC_CONNECT_EVT:
|
||||
ESP_LOGI(LOG_TAG, "Connected, cur_conn_hdl %d conn_id %d, remote "ESP_BD_ADDR_STR"", p_data->connect.conn_handle,
|
||||
p_data->connect.conn_id, ESP_BD_ADDR_HEX(p_data->connect.remote_bda));
|
||||
cur_conn_hdl = p_data->connect.conn_handle;
|
||||
break;
|
||||
case ESP_GATTC_OPEN_EVT:
|
||||
if (param->open.status != ESP_GATT_OK){
|
||||
ESP_LOGE(LOG_TAG, "Open failed, status %x", p_data->open.status);
|
||||
break;
|
||||
}
|
||||
ESP_LOGI(LOG_TAG, "Open successfully, MTU %d", p_data->open.mtu);
|
||||
gl_profile_tab[PROFILE_A_APP_ID].conn_id = p_data->open.conn_id;
|
||||
memcpy(gl_profile_tab[PROFILE_A_APP_ID].remote_bda, p_data->open.remote_bda, sizeof(esp_bd_addr_t));
|
||||
esp_err_t mtu_ret = esp_ble_gattc_send_mtu_req (gattc_if, p_data->open.conn_id);
|
||||
if (mtu_ret){
|
||||
ESP_LOGE(LOG_TAG, "config MTU error, error code = %x", mtu_ret);
|
||||
}
|
||||
break;
|
||||
case ESP_GATTC_CFG_MTU_EVT:
|
||||
ESP_LOGI(LOG_TAG, "MTU exchange, status %d, MTU %d, conn_id %d", param->cfg_mtu.status, param->cfg_mtu.mtu, param->cfg_mtu.conn_id);
|
||||
if (!param->cfg_mtu.status) {
|
||||
ESP_LOGI(LOG_TAG, "Set CTE connection receive params, conn_handle %d", cur_conn_hdl);
|
||||
cte_recv_params.conn_handle = cur_conn_hdl;
|
||||
esp_ble_cte_set_connection_receive_params(&cte_recv_params);
|
||||
}
|
||||
break;
|
||||
case ESP_GATTC_DIS_SRVC_CMPL_EVT:
|
||||
if (param->dis_srvc_cmpl.status != ESP_GATT_OK){
|
||||
ESP_LOGE(LOG_TAG, "Service discover failed, status %d", param->dis_srvc_cmpl.status);
|
||||
break;
|
||||
}
|
||||
ESP_LOGI(LOG_TAG, "Service discover complete, conn_id %d", param->dis_srvc_cmpl.conn_id);
|
||||
esp_ble_gattc_search_service(gattc_if, param->cfg_mtu.conn_id, &remote_filter_service_uuid);
|
||||
break;
|
||||
case ESP_GATTC_SEARCH_RES_EVT: {
|
||||
ESP_LOGI(LOG_TAG, "Service search result, conn_id %x, is primary service %d", p_data->search_res.conn_id, p_data->search_res.is_primary);
|
||||
ESP_LOGI(LOG_TAG, "start handle %d end handle %d current handle value %d", p_data->search_res.start_handle, p_data->search_res.end_handle, p_data->search_res.srvc_id.inst_id);
|
||||
if (p_data->search_res.srvc_id.uuid.len == ESP_UUID_LEN_16 && p_data->search_res.srvc_id.uuid.uuid.uuid16 == REMOTE_SERVICE_UUID) {
|
||||
ESP_LOGI(LOG_TAG, "UUID16: %x", p_data->search_res.srvc_id.uuid.uuid.uuid16);
|
||||
get_service = true;
|
||||
gl_profile_tab[PROFILE_A_APP_ID].service_start_handle = p_data->search_res.start_handle;
|
||||
gl_profile_tab[PROFILE_A_APP_ID].service_end_handle = p_data->search_res.end_handle;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ESP_GATTC_SEARCH_CMPL_EVT:
|
||||
if (p_data->search_cmpl.status != ESP_GATT_OK){
|
||||
ESP_LOGE(LOG_TAG, "Service search failed, status %x", p_data->search_cmpl.status);
|
||||
break;
|
||||
}
|
||||
ESP_LOGI(LOG_TAG, "Service search complete");
|
||||
if(p_data->search_cmpl.searched_service_source == ESP_GATT_SERVICE_FROM_REMOTE_DEVICE) {
|
||||
ESP_LOGI(LOG_TAG, "Get service information from remote device");
|
||||
} else if (p_data->search_cmpl.searched_service_source == ESP_GATT_SERVICE_FROM_NVS_FLASH) {
|
||||
ESP_LOGI(LOG_TAG, "Get service information from flash");
|
||||
} else {
|
||||
ESP_LOGI(LOG_TAG, "unknown service source");
|
||||
}
|
||||
|
||||
break;
|
||||
case ESP_GATTC_REG_FOR_NOTIFY_EVT: {
|
||||
if (p_data->reg_for_notify.status != ESP_GATT_OK){
|
||||
ESP_LOGE(LOG_TAG, "Notification register failed, status %x", p_data->reg_for_notify.status);
|
||||
break;
|
||||
}
|
||||
ESP_LOGI(LOG_TAG, "Notification register successfully");
|
||||
break;
|
||||
}
|
||||
case ESP_GATTC_NOTIFY_EVT:
|
||||
ESP_LOGI(LOG_TAG, "Notification received, value ");
|
||||
ESP_LOG_BUFFER_HEX(LOG_TAG, p_data->notify.value, p_data->notify.value_len);
|
||||
break;
|
||||
case ESP_GATTC_WRITE_DESCR_EVT:
|
||||
if (p_data->write.status != ESP_GATT_OK){
|
||||
ESP_LOGE(LOG_TAG, "Descriptor write failed, status %x", p_data->write.status);
|
||||
break;
|
||||
}
|
||||
ESP_LOGI(LOG_TAG, "Descriptor write successfully");
|
||||
break;
|
||||
case ESP_GATTC_SRVC_CHG_EVT: {
|
||||
esp_bd_addr_t bda;
|
||||
memcpy(bda, p_data->srvc_chg.remote_bda, sizeof(esp_bd_addr_t));
|
||||
ESP_LOGI(LOG_TAG, "Service change from "ESP_BD_ADDR_STR"", ESP_BD_ADDR_HEX(bda));
|
||||
break;
|
||||
}
|
||||
case ESP_GATTC_WRITE_CHAR_EVT:
|
||||
if (p_data->write.status != ESP_GATT_OK){
|
||||
ESP_LOGE(LOG_TAG, "Characteristic write failed, status %x", p_data->write.status);
|
||||
break;
|
||||
}
|
||||
ESP_LOGI(LOG_TAG, "Characteristic Write successfully");
|
||||
break;
|
||||
case ESP_GATTC_DISCONNECT_EVT:
|
||||
ESP_LOGI(LOG_TAG, "Disconnected, remote "ESP_BD_ADDR_STR", reason 0x%02x",
|
||||
ESP_BD_ADDR_HEX(p_data->disconnect.remote_bda), p_data->disconnect.reason);
|
||||
connect = false;
|
||||
get_service = false;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void esp_gap_cb(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param)
|
||||
{
|
||||
switch (event) {
|
||||
case ESP_GAP_BLE_SET_LOCAL_PRIVACY_COMPLETE_EVT:
|
||||
if (param->local_privacy_cmpl.status != ESP_BT_STATUS_SUCCESS){
|
||||
ESP_LOGE(LOG_TAG, "Local privacy config failed, status %x", param->local_privacy_cmpl.status);
|
||||
break;
|
||||
}
|
||||
esp_err_t scan_ret = esp_ble_gap_set_ext_scan_params(&ext_scan_params);
|
||||
if (scan_ret){
|
||||
ESP_LOGE(LOG_TAG, "Set extend scan params error, error code = %x", scan_ret);
|
||||
}
|
||||
break;
|
||||
case ESP_GAP_BLE_SET_EXT_SCAN_PARAMS_COMPLETE_EVT: {
|
||||
if (param->set_ext_scan_params.status != ESP_BT_STATUS_SUCCESS) {
|
||||
ESP_LOGE(LOG_TAG, "Extend scan parameters set failed, error status = %x", param->set_ext_scan_params.status);
|
||||
break;
|
||||
}
|
||||
//the unit of the duration is second
|
||||
esp_ble_gap_start_ext_scan(EXT_SCAN_DURATION, EXT_SCAN_PERIOD);
|
||||
break;
|
||||
}
|
||||
case ESP_GAP_BLE_EXT_SCAN_START_COMPLETE_EVT:
|
||||
if (param->ext_scan_start.status != ESP_BT_STATUS_SUCCESS) {
|
||||
ESP_LOGE(LOG_TAG, "Extended scanning start failed, status %x", param->ext_scan_start.status);
|
||||
break;
|
||||
}
|
||||
ESP_LOGI(LOG_TAG, "Extended scanning start successfully");
|
||||
break;
|
||||
case ESP_GAP_BLE_PASSKEY_REQ_EVT: /* passkey request event */
|
||||
/* Call the following function to input the passkey which is displayed on the remote device */
|
||||
//esp_ble_passkey_reply(gl_profile_tab[PROFILE_A_APP_ID].remote_bda, true, 0x00);
|
||||
ESP_LOGI(LOG_TAG, "Passkey request");
|
||||
break;
|
||||
case ESP_GAP_BLE_OOB_REQ_EVT: {
|
||||
ESP_LOGI(LOG_TAG, "OOB request");
|
||||
uint8_t tk[16] = {1}; //If you paired with OOB, both devices need to use the same tk
|
||||
esp_ble_oob_req_reply(param->ble_security.ble_req.bd_addr, tk, sizeof(tk));
|
||||
break;
|
||||
}
|
||||
case ESP_GAP_BLE_LOCAL_IR_EVT: /* BLE local IR event */
|
||||
ESP_LOGI(LOG_TAG, "Local identity root");
|
||||
break;
|
||||
case ESP_GAP_BLE_LOCAL_ER_EVT: /* BLE local ER event */
|
||||
ESP_LOGI(LOG_TAG, "Local encryption root");
|
||||
break;
|
||||
case ESP_GAP_BLE_SEC_REQ_EVT:
|
||||
/* send the positive(true) security response to the peer device to accept the security request.
|
||||
If not accept the security request, should send the security response with negative(false) accept value*/
|
||||
esp_ble_gap_security_rsp(param->ble_security.ble_req.bd_addr, true);
|
||||
break;
|
||||
case ESP_GAP_BLE_NC_REQ_EVT:
|
||||
/* The app will receive this evt when the IO has DisplayYesNO capability and the peer device IO also has DisplayYesNo capability.
|
||||
show the passkey number to the user to confirm it with the number displayed by peer device. */
|
||||
esp_ble_confirm_reply(param->ble_security.ble_req.bd_addr, true);
|
||||
ESP_LOGI(LOG_TAG, "Numeric Comparison request, passkey %" PRIu32, param->ble_security.key_notif.passkey);
|
||||
break;
|
||||
case ESP_GAP_BLE_PASSKEY_NOTIF_EVT: ///the app will receive this evt when the IO has Output capability and the peer device IO has Input capability.
|
||||
///show the passkey number to the user to input it in the peer device.
|
||||
ESP_LOGI(LOG_TAG, "Passkey notify, passkey %06" PRIu32, param->ble_security.key_notif.passkey);
|
||||
break;
|
||||
case ESP_GAP_BLE_KEY_EVT:
|
||||
//shows the ble key info share with peer device to the user.
|
||||
ESP_LOGI(LOG_TAG, "Key exchanged, key_type %s", esp_key_type_to_str(param->ble_security.ble_key.key_type));
|
||||
break;
|
||||
case ESP_GAP_BLE_AUTH_CMPL_EVT: {
|
||||
esp_bd_addr_t bd_addr;
|
||||
memcpy(bd_addr, param->ble_security.auth_cmpl.bd_addr, sizeof(esp_bd_addr_t));
|
||||
ESP_LOGI(LOG_TAG, "Authentication complete, addr_type %d, addr "ESP_BD_ADDR_STR"",
|
||||
param->ble_security.auth_cmpl.addr_type, ESP_BD_ADDR_HEX(bd_addr));
|
||||
if (!param->ble_security.auth_cmpl.success) {
|
||||
ESP_LOGI(LOG_TAG, "Pairing failed, reason 0x%x",param->ble_security.auth_cmpl.fail_reason);
|
||||
} else {
|
||||
ESP_LOGI(LOG_TAG, "Pairing successfully, auth mode %s",esp_auth_req_to_str(param->ble_security.auth_cmpl.auth_mode));
|
||||
// Enable CTE
|
||||
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ESP_GAP_BLE_EXT_ADV_REPORT_EVT: {
|
||||
uint8_t *adv_name = NULL;
|
||||
uint8_t adv_name_len = 0;
|
||||
adv_name = esp_ble_resolve_adv_data_by_type(param->ext_adv_report.params.adv_data,
|
||||
param->ext_adv_report.params.adv_data_len,
|
||||
ESP_BLE_AD_TYPE_NAME_CMPL,
|
||||
&adv_name_len);
|
||||
if (!connect && strlen(remote_device_name) == adv_name_len && strncmp((char *)adv_name, remote_device_name, adv_name_len) == 0) {
|
||||
// Note: If there are multiple devices with the same device name, the device may connect to an unintended one.
|
||||
// It is recommended to change the default device name to ensure it is unique.
|
||||
connect = true;
|
||||
esp_ble_gap_stop_ext_scan();
|
||||
ESP_LOGI(LOG_TAG, "Device found "ESP_BD_ADDR_STR"", ESP_BD_ADDR_HEX(param->ext_adv_report.params.addr));
|
||||
ESP_LOG_BUFFER_CHAR("Adv name", adv_name, adv_name_len);
|
||||
ESP_LOGI(LOG_TAG, "Stop extend scan and create aux open, primary_phy %d secondary phy %d", param->ext_adv_report.params.primary_phy, param->ext_adv_report.params.secondly_phy);
|
||||
|
||||
// create gattc virtual connection
|
||||
esp_ble_gatt_creat_conn_params_t creat_conn_params = {0};
|
||||
memcpy(&creat_conn_params.remote_bda, param->ext_adv_report.params.addr, ESP_BD_ADDR_LEN);
|
||||
creat_conn_params.remote_addr_type = param->ext_adv_report.params.addr_type;
|
||||
creat_conn_params.own_addr_type = BLE_ADDR_TYPE_PUBLIC;
|
||||
creat_conn_params.is_direct = true;
|
||||
creat_conn_params.is_aux = true;
|
||||
creat_conn_params.phy_mask = ESP_BLE_PHY_1M_PREF_MASK | ESP_BLE_PHY_2M_PREF_MASK | ESP_BLE_PHY_CODED_PREF_MASK;
|
||||
creat_conn_params.phy_1m_conn_params = &phy_1m_conn_params;
|
||||
creat_conn_params.phy_2m_conn_params = &phy_2m_conn_params;
|
||||
creat_conn_params.phy_coded_conn_params = &phy_coded_conn_params;
|
||||
esp_ble_gattc_enh_open(gl_profile_tab[PROFILE_A_APP_ID].gattc_if, &creat_conn_params);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case ESP_GAP_BLE_EXT_SCAN_STOP_COMPLETE_EVT:
|
||||
if (param->ext_scan_stop.status != ESP_BT_STATUS_SUCCESS){
|
||||
ESP_LOGE(LOG_TAG, "Scanning stop failed, status %x", param->ext_scan_stop.status);
|
||||
break;
|
||||
}
|
||||
ESP_LOGI(LOG_TAG, "Scanning stop successfully");
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void cte_event_handler(esp_ble_cte_cb_event_t event, esp_ble_cte_cb_param_t *param)
|
||||
{
|
||||
switch (event) {
|
||||
case ESP_BLE_CTE_SET_CONN_TRANS_PARAMS_CMPL_EVT:
|
||||
ESP_LOGI(LOG_TAG, "CTE set connection transmit params, status %d", param->conn_trans_params_cmpl.status);
|
||||
break;
|
||||
case ESP_BLE_CTE_SET_CONN_RECV_PARAMS_CMPL_EVT:
|
||||
ESP_LOGI(LOG_TAG, "CTE set connection receive params, status %d", param->conn_recv_params_cmpl.status);
|
||||
if (!param->conn_recv_params_cmpl.status) {
|
||||
cte_conn_req_en.conn_handle = cur_conn_hdl;
|
||||
ESP_LOGI(LOG_TAG, "Enable CTE request, conn_handle %d", cur_conn_hdl);
|
||||
esp_ble_cte_connection_cte_request_enable(&cte_conn_req_en);
|
||||
}
|
||||
break;
|
||||
case ESP_BLE_CTE_SET_CONN_REQ_ENABLE_CMPL_EVT:
|
||||
ESP_LOGI(LOG_TAG, "CTE set connection request enable, status %d", param->conn_req_en_cmpl.status);
|
||||
break;
|
||||
case ESP_BLE_CTE_SET_CONN_RSP_ENABLE_CMPL_EVT:
|
||||
ESP_LOGI(LOG_TAG, "CTE set connection response enable, status %d", param->conn_rsp_en_cmpl.status);
|
||||
break;
|
||||
case ESP_BLE_CTE_READ_ANT_INFOR_CMPL_EVT:
|
||||
ESP_LOGI(LOG_TAG, "CTE read antenna information, status %d antenna_num %d", param->read_ant_infor_cmpl.status, param->read_ant_infor_cmpl.num_antennae);
|
||||
break;
|
||||
case ESP_BLE_CTE_CONN_IQ_REPORT_EVT:
|
||||
ESP_LOGI(LOG_TAG, "conn_handle %d channel_idx %d rssi %d rssi_ant_id %d cte_type %d slot_dur %d pkt_status %d conn_evt_counter %d sample_count %d",
|
||||
param->conn_iq_rpt.conn_handle, param->conn_iq_rpt.data_channel_idx, param->conn_iq_rpt.rssi,
|
||||
param->conn_iq_rpt.rssi_ant_id, param->conn_iq_rpt.cte_type, param->conn_iq_rpt.slot_dur,
|
||||
param->conn_iq_rpt.pkt_status, param->conn_iq_rpt.conn_evt_counter, param->conn_iq_rpt.sample_count);
|
||||
|
||||
ESP_LOG_BUFFER_HEX("i_sample", ¶m->conn_iq_rpt.i_sample[0], param->conn_iq_rpt.sample_count);
|
||||
ESP_LOG_BUFFER_HEX("q_sample", ¶m->conn_iq_rpt.i_sample[0], param->conn_iq_rpt.sample_count);
|
||||
break;
|
||||
case ESP_BLE_CTE_REQUEST_FAILED_EVT:
|
||||
ESP_LOGI(LOG_TAG, "CTE connection request failed, conn_handle %d reason 0x%x", param->req_failed_evt.conn_handle, param->req_failed_evt.reason);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static void esp_gattc_cb(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param)
|
||||
{
|
||||
ESP_LOGD(LOG_TAG, "EVT %d, gattc if %d", event, gattc_if);
|
||||
|
||||
/* If event is register event, store the gattc_if for each profile */
|
||||
if (event == ESP_GATTC_REG_EVT) {
|
||||
if (param->reg.status == ESP_GATT_OK) {
|
||||
gl_profile_tab[param->reg.app_id].gattc_if = gattc_if;
|
||||
} else {
|
||||
ESP_LOGI(LOG_TAG, "Reg app failed, app_id %04x, status %d",
|
||||
param->reg.app_id,
|
||||
param->reg.status);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/* If the gattc_if equal to profile A, call profile A cb handler,
|
||||
* so here call each profile's callback */
|
||||
do {
|
||||
int idx;
|
||||
for (idx = 0; idx < PROFILE_NUM; idx++) {
|
||||
if (gattc_if == ESP_GATT_IF_NONE || /* ESP_GATT_IF_NONE, not specify a certain gatt_if, need to call every profile cb function */
|
||||
gattc_if == gl_profile_tab[idx].gattc_if) {
|
||||
if (gl_profile_tab[idx].gattc_cb) {
|
||||
gl_profile_tab[idx].gattc_cb(event, gattc_if, param);
|
||||
}
|
||||
}
|
||||
}
|
||||
} while (0);
|
||||
}
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
// Initialize NVS.
|
||||
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 );
|
||||
|
||||
#if CONFIG_EXAMPLE_CI_PIPELINE_ID
|
||||
memcpy(remote_device_name, esp_bluedroid_get_example_name(), sizeof(remote_device_name));
|
||||
#endif
|
||||
|
||||
ESP_ERROR_CHECK(esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT));
|
||||
|
||||
esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT();
|
||||
ret = esp_bt_controller_init(&bt_cfg);
|
||||
if (ret) {
|
||||
ESP_LOGE(LOG_TAG, "%s initialize controller failed: %s", __func__, esp_err_to_name(ret));
|
||||
return;
|
||||
}
|
||||
|
||||
ret = esp_bt_controller_enable(ESP_BT_MODE_BLE);
|
||||
if (ret) {
|
||||
ESP_LOGE(LOG_TAG, "%s enable controller failed: %s", __func__, esp_err_to_name(ret));
|
||||
return;
|
||||
}
|
||||
|
||||
esp_bluedroid_config_t cfg = BT_BLUEDROID_INIT_CONFIG_DEFAULT();
|
||||
ret = esp_bluedroid_init_with_cfg(&cfg);
|
||||
if (ret) {
|
||||
ESP_LOGE(LOG_TAG, "%s init bluetooth failed: %s", __func__, esp_err_to_name(ret));
|
||||
return;
|
||||
}
|
||||
|
||||
ret = esp_bluedroid_enable();
|
||||
if (ret) {
|
||||
ESP_LOGE(LOG_TAG, "%s enable bluetooth failed: %s", __func__, esp_err_to_name(ret));
|
||||
return;
|
||||
}
|
||||
|
||||
ret = esp_ble_cte_register_callback(cte_event_handler);
|
||||
if (ret){
|
||||
ESP_LOGE(LOG_TAG, "CTE callback register error, error code = %x", ret);
|
||||
return;
|
||||
}
|
||||
|
||||
//register the callback function to the gap module
|
||||
ret = esp_ble_gap_register_callback(esp_gap_cb);
|
||||
if (ret){
|
||||
ESP_LOGE(LOG_TAG, "%s gap register error, error code = %x", __func__, ret);
|
||||
return;
|
||||
}
|
||||
|
||||
//register the callback function to the gattc module
|
||||
ret = esp_ble_gattc_register_callback(esp_gattc_cb);
|
||||
if(ret){
|
||||
ESP_LOGE(LOG_TAG, "%s gattc register error, error code = %x", __func__, ret);
|
||||
return;
|
||||
}
|
||||
|
||||
ret = esp_ble_gattc_app_register(PROFILE_A_APP_ID);
|
||||
if (ret){
|
||||
ESP_LOGE(LOG_TAG, "%s gattc app register error, error code = %x", __func__, ret);
|
||||
}
|
||||
|
||||
ret = esp_ble_gatt_set_local_mtu(500);
|
||||
if (ret){
|
||||
ESP_LOGE(LOG_TAG, "set local MTU failed, error code = %x", ret);
|
||||
}
|
||||
|
||||
/* set the security iocap & auth_req & key size & init key response key parameters to the stack*/
|
||||
esp_ble_auth_req_t auth_req = ESP_LE_AUTH_REQ_SC_MITM_BOND; //bonding with peer device after authentication
|
||||
esp_ble_io_cap_t iocap = ESP_IO_CAP_NONE; //set the IO capability to No output No input
|
||||
uint8_t key_size = 16; //the key size should be 7~16 bytes
|
||||
uint8_t init_key = ESP_BLE_ENC_KEY_MASK | ESP_BLE_ID_KEY_MASK;
|
||||
uint8_t rsp_key = ESP_BLE_ENC_KEY_MASK | ESP_BLE_ID_KEY_MASK;
|
||||
uint8_t oob_support = ESP_BLE_OOB_DISABLE;
|
||||
esp_ble_gap_set_security_param(ESP_BLE_SM_AUTHEN_REQ_MODE, &auth_req, sizeof(uint8_t));
|
||||
esp_ble_gap_set_security_param(ESP_BLE_SM_IOCAP_MODE, &iocap, sizeof(uint8_t));
|
||||
esp_ble_gap_set_security_param(ESP_BLE_SM_MAX_KEY_SIZE, &key_size, sizeof(uint8_t));
|
||||
esp_ble_gap_set_security_param(ESP_BLE_SM_OOB_SUPPORT, &oob_support, sizeof(uint8_t));
|
||||
/* If your BLE device act as a Slave, the init_key means you hope which types of key of the master should distribute to you,
|
||||
and the response key means which key you can distribute to the Master;
|
||||
If your BLE device act as a master, the response key means you hope which types of key of the slave should distribute to you,
|
||||
and the init key means which key you can distribute to the slave. */
|
||||
esp_ble_gap_set_security_param(ESP_BLE_SM_SET_INIT_KEY, &init_key, sizeof(uint8_t));
|
||||
esp_ble_gap_set_security_param(ESP_BLE_SM_SET_RSP_KEY, &rsp_key, sizeof(uint8_t));
|
||||
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
# This file was generated using idf.py save-defconfig. It can be edited manually.
|
||||
# Espressif IoT Development Framework (ESP-IDF) Project Minimal Configuration
|
||||
#
|
||||
CONFIG_BT_ENABLED=y
|
||||
|
||||
CONFIG_BT_BLE_FEAT_CTE_EN=y
|
||||
|
||||
CONFIG_BT_BLE_FEAT_CTE_CONNECTION_EN=y
|
||||
|
||||
CONFIG_BT_BLE_FEAT_CTE_CONNECTIONLESS_EN=n
|
||||
|
||||
CONFIG_BT_BLE_SMP_ENABLE=y
|
||||
|
||||
CONFIG_BT_GATTC_ENABLE=y
|
||||
|
||||
CONFIG_BT_GATTS_ENABLE=n
|
||||
|
||||
CONFIG_BT_BLE_50_EXTEND_ADV_EN=n
|
||||
|
||||
CONFIG_BT_BLE_50_EXTEND_SYNC_EN=n
|
||||
|
||||
CONFIG_BT_BLE_50_DTM_TEST_EN=n
|
||||
@@ -0,0 +1,8 @@
|
||||
# The following lines of boilerplate have to be in your project's CMakeLists
|
||||
# in this exact order for cmake to work correctly
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
# "Trim" the build. Include the minimal set of components, main, and anything it depends on.
|
||||
idf_build_set_property(MINIMAL_BUILD ON)
|
||||
project(connection_peripheral_with_cte_demo)
|
||||
@@ -0,0 +1,78 @@
|
||||
| Supported Targets | ESP32-C5 | ESP32-C61 | ESP32-H2 |
|
||||
| ----------------- | -------- | --------- | -------- |
|
||||
|
||||
# ESP-IDF BLE Connection Peripheral with CTE Example
|
||||
|
||||
This example demonstrates how to use BLE Constant Tone Extension (CTE) feature as a Peripheral device using Bluedroid host API.
|
||||
|
||||
## What is BLE CTE?
|
||||
|
||||
BLE Constant Tone Extension (CTE) is a BLE 5.0 feature that enables Direction Finding (AoA/AoD) functionality. CTE allows devices to determine the direction of a Bluetooth signal by analyzing the phase of the received signal.
|
||||
|
||||
## How It Works
|
||||
|
||||
1. The Peripheral device starts extended advertising
|
||||
2. When a Central device connects to it
|
||||
3. After connection, the Peripheral configures CTE transmit parameters
|
||||
4. The Peripheral enables CTE response to transmit CTE to the Central
|
||||
5. The Peripheral receives IQ samples for direction finding
|
||||
|
||||
## API Usage
|
||||
|
||||
- `esp_ble_cte_register_callback()` - Register CTE event callback
|
||||
- `esp_ble_cte_set_connection_transmit_params()` - Set CTE transmit parameters for connection
|
||||
- `esp_ble_cte_connection_cte_response_enable()` - Enable CTE response on connection
|
||||
- `ESP_BLE_CTE_CONN_IQ_REPORT_EVT` - Event received when IQ samples are available
|
||||
|
||||
## Configuration
|
||||
|
||||
The example requires BLE 5.0 CTE features to be enabled:
|
||||
- `CONFIG_BT_BLE_50_FEATURES_SUPPORTED=y`
|
||||
- `CONFIG_BT_BLE_FEAT_CTE_EN=y`
|
||||
- `CONFIG_BT_LE_CTE_FEATURE_ENABLED=y`
|
||||
|
||||
## Hardware Required
|
||||
|
||||
* A development board with ESP32-C5, ESP32-C61, or ESP32-H2 SoC that supports BLE 5.0 CTE
|
||||
* A USB cable for Power supply and programming
|
||||
|
||||
See [Development Boards](https://www.espressif.com/en/products/devkits) for more information about it.
|
||||
|
||||
## How to Use Example
|
||||
|
||||
Before project configuration and build, be sure to set the correct chip target using:
|
||||
|
||||
```bash
|
||||
idf.py set-target esp32c5 # or esp32c61 or esp32h2
|
||||
```
|
||||
|
||||
To test this demo, you can run the [ble_connection_central_with_cte](../ble_connection_central_with_cte) example on another device, which will scan and connect to this demo automatically.
|
||||
|
||||
### 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
|
||||
|
||||
The example will output CTE-related events including:
|
||||
- Extended advertising start completion
|
||||
- Connection establishment
|
||||
- CTE parameter configuration completion
|
||||
- CTE response enable completion
|
||||
- IQ sample reports for direction finding
|
||||
|
||||
```
|
||||
I (xxx) CTE_TEST: Extended advertising started
|
||||
I (xxx) CTE_TEST: Connection established
|
||||
I (xxx) CTE_TEST: CTE set connection transmit params, status 0
|
||||
I (xxx) CTE_TEST: CTE set connection response enable, status 0
|
||||
I (xxx) CTE_TEST: CTE connection IQ report received
|
||||
...
|
||||
```
|
||||
## 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 "connection_peripheral_with_cte.c"
|
||||
PRIV_REQUIRES bt nvs_flash
|
||||
INCLUDE_DIRS ".")
|
||||
@@ -0,0 +1,567 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Unlicense OR CC0-1.0
|
||||
*/
|
||||
|
||||
#include <inttypes.h>
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "freertos/event_groups.h"
|
||||
#include "esp_system.h"
|
||||
#include "esp_log.h"
|
||||
#include "nvs_flash.h"
|
||||
#include "esp_bt.h"
|
||||
|
||||
#include "esp_gap_ble_api.h"
|
||||
#include "esp_gatts_api.h"
|
||||
#include "esp_bt_defs.h"
|
||||
#include "esp_bt_main.h"
|
||||
#include "connection_peripheral_with_cte.h"
|
||||
#include "esp_ble_cte_api.h"
|
||||
|
||||
#define LOG_TAG "CTE_TEST"
|
||||
|
||||
#define HEART_PROFILE_NUM 1
|
||||
#define HEART_PROFILE_APP_IDX 0
|
||||
#define ESP_HEART_RATE_APP_ID 0x55
|
||||
#define HEART_RATE_SVC_INST_ID 0
|
||||
#define EXT_ADV_HANDLE 0
|
||||
#define NUM_EXT_ADV_SET 1
|
||||
#define EXT_ADV_DURATION 0
|
||||
#define EXT_ADV_MAX_EVENTS 0
|
||||
#define EXT_ADV_NAME_LEN_OFFSET 10
|
||||
#define EXT_ADV_NAME_OFFSET 12
|
||||
|
||||
#define GATTS_DEMO_CHAR_VAL_LEN_MAX 0x40
|
||||
|
||||
#ifndef MIN
|
||||
#define MIN(a, b) (((a) < (b)) ? (a) : (b))
|
||||
#endif
|
||||
|
||||
static uint16_t profile_handle_table[HRS_IDX_NB];
|
||||
|
||||
static uint8_t ext_adv_raw_data[] = {
|
||||
0x02, 0x01, 0x06,
|
||||
0x02, 0x0a, 0xeb, 0x03, 0x03, 0xab, 0xcd,
|
||||
0x11, 0X09, 'E', 'S', 'P', '_', 'C', 'O', 'N', '_', 'C', 'T', 'E', '_', 'T', 'E', 'S', 'T',
|
||||
};
|
||||
|
||||
static esp_ble_gap_ext_adv_t ext_adv[1] = {
|
||||
[0] = {EXT_ADV_HANDLE, EXT_ADV_DURATION, EXT_ADV_MAX_EVENTS},
|
||||
};
|
||||
|
||||
esp_ble_gap_ext_adv_params_t ext_adv_params_2M = {
|
||||
.type = ESP_BLE_GAP_SET_EXT_ADV_PROP_CONNECTABLE,
|
||||
.interval_min = 0x20,
|
||||
.interval_max = 0x20,
|
||||
.channel_map = ADV_CHNL_ALL,
|
||||
.filter_policy = ADV_FILTER_ALLOW_SCAN_ANY_CON_ANY,
|
||||
.primary_phy = ESP_BLE_GAP_PHY_1M,
|
||||
.max_skip = 0,
|
||||
.secondary_phy = ESP_BLE_GAP_PHY_2M,
|
||||
.sid = 0,
|
||||
.scan_req_notif = false,
|
||||
.own_addr_type = BLE_ADDR_TYPE_PUBLIC,
|
||||
.tx_power = EXT_ADV_TX_PWR_NO_PREFERENCE,
|
||||
};
|
||||
|
||||
static uint8_t antenna_ids[2] = {0x00, 0x01};
|
||||
static esp_ble_cte_conn_trans_params_t cte_conn_trans_params = {
|
||||
.conn_handle = 0xff,
|
||||
.cte_types = ESP_BLE_CTE_TYPES_ALL,
|
||||
.switching_pattern_len = sizeof(antenna_ids),
|
||||
.antenna_ids = &antenna_ids[0],
|
||||
};
|
||||
|
||||
static esp_ble_cte_rsp_en_params_t cte_conn_rsp_en = {
|
||||
.conn_handle = 0xff,
|
||||
.enable = ESP_BLE_CTE_RESPONSE_FOR_CONNECTION_ENABLE,
|
||||
};
|
||||
|
||||
uint16_t cur_conn_hdl = 0xff;
|
||||
|
||||
struct gatts_profile_inst {
|
||||
esp_gatts_cb_t gatts_cb;
|
||||
uint16_t gatts_if;
|
||||
uint16_t app_id;
|
||||
uint16_t conn_id;
|
||||
uint16_t service_handle;
|
||||
esp_gatt_srvc_id_t service_id;
|
||||
uint16_t char_handle;
|
||||
esp_bt_uuid_t char_uuid;
|
||||
esp_gatt_perm_t perm;
|
||||
esp_gatt_char_prop_t property;
|
||||
uint16_t descr_handle;
|
||||
esp_bt_uuid_t descr_uuid;
|
||||
};
|
||||
|
||||
static void gatts_profile_event_handler(esp_gatts_cb_event_t event,
|
||||
esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param);
|
||||
|
||||
/* One gatt-based profile one app_id and one gatts_if, this array will store the gatts_if returned by ESP_GATTS_REG_EVT */
|
||||
static struct gatts_profile_inst heart_rate_profile_tab[HEART_PROFILE_NUM] = {
|
||||
[HEART_PROFILE_APP_IDX] = {
|
||||
.gatts_cb = gatts_profile_event_handler,
|
||||
.gatts_if = ESP_GATT_IF_NONE, /* Not get the gatt_if, so initial is ESP_GATT_IF_NONE */
|
||||
},
|
||||
|
||||
};
|
||||
|
||||
/* Service */
|
||||
static const uint16_t GATTS_SERVICE_UUID_TEST = 0x00FF;
|
||||
static const uint16_t GATTS_CHAR_UUID_TEST_A = 0xFF01;
|
||||
static const uint16_t GATTS_CHAR_UUID_TEST_B = 0xFF02;
|
||||
static const uint16_t GATTS_CHAR_UUID_TEST_C = 0xFF03;
|
||||
|
||||
static const uint16_t primary_service_uuid = ESP_GATT_UUID_PRI_SERVICE;
|
||||
static const uint16_t character_declaration_uuid = ESP_GATT_UUID_CHAR_DECLARE;
|
||||
static const uint16_t character_client_config_uuid = ESP_GATT_UUID_CHAR_CLIENT_CONFIG;
|
||||
static const uint8_t char_prop_read = ESP_GATT_CHAR_PROP_BIT_READ;
|
||||
static const uint8_t char_prop_write = ESP_GATT_CHAR_PROP_BIT_WRITE;
|
||||
static const uint8_t char_prop_read_write_notify = ESP_GATT_CHAR_PROP_BIT_WRITE | ESP_GATT_CHAR_PROP_BIT_READ | ESP_GATT_CHAR_PROP_BIT_NOTIFY;
|
||||
static const uint8_t heart_measurement_ccc[2] = {0x00, 0x00};
|
||||
static const uint8_t char_value[4] = {0x11, 0x22, 0x33, 0x44};
|
||||
#define CHAR_DECLARATION_SIZE (sizeof(uint8_t))
|
||||
#define SVC_INST_ID 0
|
||||
|
||||
|
||||
/* Full Database Description - Used to add attributes into the database */
|
||||
static const esp_gatts_attr_db_t gatt_db[HRS_IDX_NB] =
|
||||
{
|
||||
// Service Declaration
|
||||
[IDX_SVC] =
|
||||
{{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&primary_service_uuid, ESP_GATT_PERM_READ,
|
||||
sizeof(uint16_t), sizeof(GATTS_SERVICE_UUID_TEST), (uint8_t *)&GATTS_SERVICE_UUID_TEST}},
|
||||
|
||||
/* Characteristic Declaration */
|
||||
[IDX_CHAR_A] =
|
||||
{{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&character_declaration_uuid, ESP_GATT_PERM_READ,
|
||||
CHAR_DECLARATION_SIZE, CHAR_DECLARATION_SIZE, (uint8_t *)&char_prop_read_write_notify}},
|
||||
|
||||
/* Characteristic Value */
|
||||
[IDX_CHAR_VAL_A] =
|
||||
{{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&GATTS_CHAR_UUID_TEST_A, ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE,
|
||||
GATTS_DEMO_CHAR_VAL_LEN_MAX, sizeof(char_value), (uint8_t *)char_value}},
|
||||
|
||||
/* Client Characteristic Configuration Descriptor */
|
||||
[IDX_CHAR_CFG_A] =
|
||||
{{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&character_client_config_uuid, ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE,
|
||||
sizeof(uint16_t), sizeof(heart_measurement_ccc), (uint8_t *)heart_measurement_ccc}},
|
||||
|
||||
/* Characteristic Declaration */
|
||||
[IDX_CHAR_B] =
|
||||
{{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&character_declaration_uuid, ESP_GATT_PERM_READ,
|
||||
CHAR_DECLARATION_SIZE, CHAR_DECLARATION_SIZE, (uint8_t *)&char_prop_read}},
|
||||
|
||||
/* Characteristic Value */
|
||||
[IDX_CHAR_VAL_B] =
|
||||
{{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&GATTS_CHAR_UUID_TEST_B, ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE,
|
||||
GATTS_DEMO_CHAR_VAL_LEN_MAX, sizeof(char_value), (uint8_t *)char_value}},
|
||||
|
||||
/* Characteristic Declaration */
|
||||
[IDX_CHAR_C] =
|
||||
{{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&character_declaration_uuid, ESP_GATT_PERM_READ,
|
||||
CHAR_DECLARATION_SIZE, CHAR_DECLARATION_SIZE, (uint8_t *)&char_prop_write}},
|
||||
|
||||
/* Characteristic Value */
|
||||
[IDX_CHAR_VAL_C] =
|
||||
{{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&GATTS_CHAR_UUID_TEST_C, ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE,
|
||||
GATTS_DEMO_CHAR_VAL_LEN_MAX, sizeof(char_value), (uint8_t *)char_value}},
|
||||
|
||||
};
|
||||
|
||||
static char *esp_key_type_to_str(esp_ble_key_type_t key_type)
|
||||
{
|
||||
char *key_str = NULL;
|
||||
switch(key_type) {
|
||||
case ESP_LE_KEY_NONE:
|
||||
key_str = "ESP_LE_KEY_NONE";
|
||||
break;
|
||||
case ESP_LE_KEY_PENC:
|
||||
key_str = "ESP_LE_KEY_PENC";
|
||||
break;
|
||||
case ESP_LE_KEY_PID:
|
||||
key_str = "ESP_LE_KEY_PID";
|
||||
break;
|
||||
case ESP_LE_KEY_PCSRK:
|
||||
key_str = "ESP_LE_KEY_PCSRK";
|
||||
break;
|
||||
case ESP_LE_KEY_PLK:
|
||||
key_str = "ESP_LE_KEY_PLK";
|
||||
break;
|
||||
case ESP_LE_KEY_LLK:
|
||||
key_str = "ESP_LE_KEY_LLK";
|
||||
break;
|
||||
case ESP_LE_KEY_LENC:
|
||||
key_str = "ESP_LE_KEY_LENC";
|
||||
break;
|
||||
case ESP_LE_KEY_LID:
|
||||
key_str = "ESP_LE_KEY_LID";
|
||||
break;
|
||||
case ESP_LE_KEY_LCSRK:
|
||||
key_str = "ESP_LE_KEY_LCSRK";
|
||||
break;
|
||||
default:
|
||||
key_str = "INVALID BLE KEY TYPE";
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
return key_str;
|
||||
}
|
||||
|
||||
static char *esp_auth_req_to_str(esp_ble_auth_req_t auth_req)
|
||||
{
|
||||
char *auth_str = NULL;
|
||||
switch(auth_req) {
|
||||
case ESP_LE_AUTH_NO_BOND:
|
||||
auth_str = "ESP_LE_AUTH_NO_BOND";
|
||||
break;
|
||||
case ESP_LE_AUTH_BOND:
|
||||
auth_str = "ESP_LE_AUTH_BOND";
|
||||
break;
|
||||
case ESP_LE_AUTH_REQ_MITM:
|
||||
auth_str = "ESP_LE_AUTH_REQ_MITM";
|
||||
break;
|
||||
case ESP_LE_AUTH_REQ_BOND_MITM:
|
||||
auth_str = "ESP_LE_AUTH_REQ_BOND_MITM";
|
||||
break;
|
||||
case ESP_LE_AUTH_REQ_SC_ONLY:
|
||||
auth_str = "ESP_LE_AUTH_REQ_SC_ONLY";
|
||||
break;
|
||||
case ESP_LE_AUTH_REQ_SC_BOND:
|
||||
auth_str = "ESP_LE_AUTH_REQ_SC_BOND";
|
||||
break;
|
||||
case ESP_LE_AUTH_REQ_SC_MITM:
|
||||
auth_str = "ESP_LE_AUTH_REQ_SC_MITM";
|
||||
break;
|
||||
case ESP_LE_AUTH_REQ_SC_MITM_BOND:
|
||||
auth_str = "ESP_LE_AUTH_REQ_SC_MITM_BOND";
|
||||
break;
|
||||
default:
|
||||
auth_str = "INVALID BLE AUTH REQ";
|
||||
break;
|
||||
}
|
||||
|
||||
return auth_str;
|
||||
}
|
||||
|
||||
static void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param)
|
||||
{
|
||||
switch (event) {
|
||||
case ESP_GAP_BLE_EXT_ADV_SET_PARAMS_COMPLETE_EVT:
|
||||
ESP_LOGI(LOG_TAG,"Extended advertising params set, status %d", param->ext_adv_set_params.status);
|
||||
esp_ble_gap_config_ext_adv_data_raw(EXT_ADV_HANDLE, sizeof(ext_adv_raw_data), &ext_adv_raw_data[0]);
|
||||
break;
|
||||
case ESP_GAP_BLE_EXT_ADV_DATA_SET_COMPLETE_EVT:
|
||||
ESP_LOGI(LOG_TAG,"Extended advertising data set, status %d", param->ext_adv_data_set.status);
|
||||
esp_ble_gap_ext_adv_start(NUM_EXT_ADV_SET, &ext_adv[0]);
|
||||
break;
|
||||
case ESP_GAP_BLE_EXT_ADV_START_COMPLETE_EVT:
|
||||
ESP_LOGI(LOG_TAG, "Extended advertising start, status %d", param->ext_adv_data_set.status);
|
||||
break;
|
||||
case ESP_GAP_BLE_ADV_TERMINATED_EVT:
|
||||
ESP_LOGI(LOG_TAG, "Extended advertising terminated, status %d", param->adv_terminate.status);
|
||||
if(param->adv_terminate.status == 0x00) {
|
||||
ESP_LOGI(LOG_TAG, "Advertising successfully ended with a connection being created");
|
||||
}
|
||||
break;
|
||||
case ESP_GAP_BLE_PASSKEY_REQ_EVT: /* passkey request event */
|
||||
/* Call the following function to input the passkey which is displayed on the remote device */
|
||||
ESP_LOGI(LOG_TAG, "Passkey request");
|
||||
//esp_ble_passkey_reply(heart_rate_profile_tab[HEART_PROFILE_APP_IDX].remote_bda, true, 0x00);
|
||||
break;
|
||||
case ESP_GAP_BLE_OOB_REQ_EVT: {
|
||||
ESP_LOGI(LOG_TAG, "OOB request");
|
||||
uint8_t tk[16] = {1}; //If you paired with OOB, both devices need to use the same tk
|
||||
esp_ble_oob_req_reply(param->ble_security.ble_req.bd_addr, tk, sizeof(tk));
|
||||
break;
|
||||
}
|
||||
case ESP_GAP_BLE_LOCAL_IR_EVT: /* BLE local IR event */
|
||||
ESP_LOGI(LOG_TAG, "Local identity root");
|
||||
break;
|
||||
case ESP_GAP_BLE_LOCAL_ER_EVT: /* BLE local ER event */
|
||||
ESP_LOGI(LOG_TAG, "Local encryption root");
|
||||
break;
|
||||
case ESP_GAP_BLE_NC_REQ_EVT:
|
||||
/* The app will receive this evt when the IO has DisplayYesNO capability and the peer device IO also has DisplayYesNo capability.
|
||||
show the passkey number to the user to confirm it with the number displayed by peer device. */
|
||||
esp_ble_confirm_reply(param->ble_security.ble_req.bd_addr, true);
|
||||
ESP_LOGI(LOG_TAG, "Numeric Comparison request, passkey %" PRIu32, param->ble_security.key_notif.passkey);
|
||||
break;
|
||||
case ESP_GAP_BLE_SEC_REQ_EVT:
|
||||
/* send the positive(true) security response to the peer device to accept the security request.
|
||||
If not accept the security request, should send the security response with negative(false) accept value*/
|
||||
esp_ble_gap_security_rsp(param->ble_security.ble_req.bd_addr, true);
|
||||
break;
|
||||
case ESP_GAP_BLE_PASSKEY_NOTIF_EVT: ///the app will receive this evt when the IO has Output capability and the peer device IO has Input capability.
|
||||
///show the passkey number to the user to input it in the peer device.
|
||||
ESP_LOGI(LOG_TAG, "Passkey notify, passkey %06" PRIu32, param->ble_security.key_notif.passkey);
|
||||
break;
|
||||
case ESP_GAP_BLE_KEY_EVT:
|
||||
//shows the ble key info share with peer device to the user.
|
||||
ESP_LOGI(LOG_TAG, "Key exchanged, key_type %s", esp_key_type_to_str(param->ble_security.ble_key.key_type));
|
||||
if (param->ble_security.ble_key.key_type == ESP_LE_KEY_PID) {
|
||||
ESP_LOGI(LOG_TAG, "peer addr "ESP_BD_ADDR_STR"", ESP_BD_ADDR_HEX(param->ble_security.ble_key.bd_addr));
|
||||
}
|
||||
break;
|
||||
case ESP_GAP_BLE_AUTH_CMPL_EVT: {
|
||||
esp_bd_addr_t bd_addr;
|
||||
memcpy(bd_addr, param->ble_security.auth_cmpl.bd_addr, sizeof(esp_bd_addr_t));
|
||||
ESP_LOGI(LOG_TAG, "Authentication complete, addr_type %u, addr "ESP_BD_ADDR_STR"",
|
||||
param->ble_security.auth_cmpl.addr_type, ESP_BD_ADDR_HEX(bd_addr));
|
||||
if(!param->ble_security.auth_cmpl.success) {
|
||||
ESP_LOGI(LOG_TAG, "Pairing failed, reason 0x%x",param->ble_security.auth_cmpl.fail_reason);
|
||||
} else {
|
||||
ESP_LOGI(LOG_TAG, "Pairing successfully, auth_mode %s",esp_auth_req_to_str(param->ble_security.auth_cmpl.auth_mode));
|
||||
// Setting CTE connection transmit parameters
|
||||
cte_conn_trans_params.conn_handle = cur_conn_hdl;
|
||||
ESP_LOGI(LOG_TAG, "Set CTE connection transmit params, conn_handle %d", cur_conn_hdl);
|
||||
esp_ble_cte_set_connection_transmit_params(&cte_conn_trans_params);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ESP_GAP_BLE_REMOVE_BOND_DEV_COMPLETE_EVT: {
|
||||
ESP_LOGD(LOG_TAG, "Bond device remove, status %d, device "ESP_BD_ADDR_STR"",
|
||||
param->remove_bond_dev_cmpl.status, ESP_BD_ADDR_HEX(param->remove_bond_dev_cmpl.bd_addr));
|
||||
break;
|
||||
}
|
||||
case ESP_GAP_BLE_SET_LOCAL_PRIVACY_COMPLETE_EVT:
|
||||
ESP_LOGI(LOG_TAG, "Local privacy config, status %x", param->local_privacy_cmpl.status);
|
||||
esp_ble_gap_ext_adv_set_params(EXT_ADV_HANDLE, &ext_adv_params_2M);
|
||||
break;
|
||||
case ESP_GAP_BLE_UPDATE_CONN_PARAMS_EVT:
|
||||
ESP_LOGI(LOG_TAG, "Connection params update, status %d, conn_int %d, latency %d, timeout %d",
|
||||
param->update_conn_params.status,
|
||||
param->update_conn_params.conn_int,
|
||||
param->update_conn_params.latency,
|
||||
param->update_conn_params.timeout);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void gatts_profile_event_handler(esp_gatts_cb_event_t event,
|
||||
esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param)
|
||||
{
|
||||
switch (event) {
|
||||
case ESP_GATTS_REG_EVT:
|
||||
ESP_LOGI(LOG_TAG, "GATT server register, status %d", param->reg.status);
|
||||
//generate a resolvable random address
|
||||
esp_ble_gap_config_local_privacy(true);
|
||||
esp_ble_gatts_create_attr_tab(gatt_db, gatts_if, HRS_IDX_NB, SVC_INST_ID);
|
||||
break;
|
||||
case ESP_GATTS_READ_EVT:
|
||||
ESP_LOGI(LOG_TAG, "Characteristic read");
|
||||
break;
|
||||
case ESP_GATTS_WRITE_EVT:
|
||||
ESP_LOGI(LOG_TAG, "Characteristic write, value ");
|
||||
ESP_LOG_BUFFER_HEX(LOG_TAG, param->write.value, param->write.len);
|
||||
break;
|
||||
case ESP_GATTS_EXEC_WRITE_EVT:
|
||||
break;
|
||||
case ESP_GATTS_MTU_EVT:
|
||||
break;
|
||||
case ESP_GATTS_CONF_EVT:
|
||||
break;
|
||||
case ESP_GATTS_UNREG_EVT:
|
||||
break;
|
||||
case ESP_GATTS_DELETE_EVT:
|
||||
break;
|
||||
case ESP_GATTS_START_EVT:
|
||||
break;
|
||||
case ESP_GATTS_STOP_EVT:
|
||||
break;
|
||||
case ESP_GATTS_CONNECT_EVT:
|
||||
ESP_LOGI(LOG_TAG, "Connected, conn_id %u, remote "ESP_BD_ADDR_STR"",
|
||||
param->connect.conn_id, ESP_BD_ADDR_HEX(param->connect.remote_bda));
|
||||
// Save conn_handle for CTE
|
||||
cur_conn_hdl = param->connect.conn_handle;
|
||||
/* start security connect with peer device when receive the connect event sent by the master */
|
||||
esp_ble_set_encryption(param->connect.remote_bda, ESP_BLE_SEC_ENCRYPT_MITM);
|
||||
break;
|
||||
case ESP_GATTS_DISCONNECT_EVT:
|
||||
ESP_LOGI(LOG_TAG, "Disconnected, remote "ESP_BD_ADDR_STR", reason 0x%x",
|
||||
ESP_BD_ADDR_HEX(param->disconnect.remote_bda), param->disconnect.reason);
|
||||
/* start advertising again when missing the connect */
|
||||
esp_ble_gap_ext_adv_start(NUM_EXT_ADV_SET, &ext_adv[0]);
|
||||
break;
|
||||
case ESP_GATTS_OPEN_EVT:
|
||||
break;
|
||||
case ESP_GATTS_CANCEL_OPEN_EVT:
|
||||
break;
|
||||
case ESP_GATTS_CLOSE_EVT:
|
||||
break;
|
||||
case ESP_GATTS_LISTEN_EVT:
|
||||
break;
|
||||
case ESP_GATTS_CONGEST_EVT:
|
||||
break;
|
||||
case ESP_GATTS_CREAT_ATTR_TAB_EVT: {
|
||||
if (param->create.status == ESP_GATT_OK){
|
||||
if(param->add_attr_tab.num_handle == HRS_IDX_NB) {
|
||||
ESP_LOGI(LOG_TAG, "Attribute table create successfully, num_handle %x", param->add_attr_tab.num_handle);
|
||||
memcpy(profile_handle_table, param->add_attr_tab.handles,
|
||||
sizeof(profile_handle_table));
|
||||
esp_ble_gatts_start_service(profile_handle_table[IDX_SVC]);
|
||||
}else{
|
||||
ESP_LOGE(LOG_TAG, "Attribute table create abnormally, num_handle (%d) doesn't equal to HRS_IDX_NB(%d)",
|
||||
param->add_attr_tab.num_handle, HRS_IDX_NB);
|
||||
}
|
||||
}else{
|
||||
ESP_LOGE(LOG_TAG, "Attribute table create failed, status %x", param->create.status);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void cte_event_handler(esp_ble_cte_cb_event_t event, esp_ble_cte_cb_param_t *param)
|
||||
{
|
||||
switch (event) {
|
||||
case ESP_BLE_CTE_SET_CONN_TRANS_PARAMS_CMPL_EVT:
|
||||
ESP_LOGI(LOG_TAG, "CTE set connection transmit params, status %d", param->conn_trans_params_cmpl.status);
|
||||
if (!param->conn_trans_params_cmpl.status) {
|
||||
ESP_LOGI(LOG_TAG, "Setting CTE connection response enable");
|
||||
// Enable CTE response for connection
|
||||
cte_conn_rsp_en.conn_handle = cur_conn_hdl;
|
||||
esp_ble_cte_connection_cte_response_enable(&cte_conn_rsp_en);
|
||||
}
|
||||
break;
|
||||
case ESP_BLE_CTE_SET_CONN_RECV_PARAMS_CMPL_EVT:
|
||||
ESP_LOGI(LOG_TAG, "CTE set connection receive params, status %d", param->conn_recv_params_cmpl.status);
|
||||
break;
|
||||
case ESP_BLE_CTE_SET_CONN_REQ_ENABLE_CMPL_EVT:
|
||||
ESP_LOGI(LOG_TAG, "CTE set connection request enable, status %d", param->conn_req_en_cmpl.status);
|
||||
break;
|
||||
case ESP_BLE_CTE_SET_CONN_RSP_ENABLE_CMPL_EVT:
|
||||
ESP_LOGI(LOG_TAG, "CTE set connection response enable, status %d", param->conn_rsp_en_cmpl.status);
|
||||
break;
|
||||
case ESP_BLE_CTE_READ_ANT_INFOR_CMPL_EVT:
|
||||
break;
|
||||
case ESP_BLE_CTE_CONN_IQ_REPORT_EVT:
|
||||
break;
|
||||
case ESP_BLE_CTE_REQUEST_FAILED_EVT:
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static void gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if,
|
||||
esp_ble_gatts_cb_param_t *param)
|
||||
{
|
||||
/* If event is register event, store the gatts_if for each profile */
|
||||
if (event == ESP_GATTS_REG_EVT) {
|
||||
if (param->reg.status == ESP_GATT_OK) {
|
||||
heart_rate_profile_tab[HEART_PROFILE_APP_IDX].gatts_if = gatts_if;
|
||||
} else {
|
||||
ESP_LOGI(LOG_TAG, "Reg app failed, app_id %04x, status %d",
|
||||
param->reg.app_id,
|
||||
param->reg.status);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
do {
|
||||
int idx;
|
||||
for (idx = 0; idx < HEART_PROFILE_NUM; idx++) {
|
||||
if (gatts_if == ESP_GATT_IF_NONE || /* ESP_GATT_IF_NONE, not specify a certain gatt_if, need to call every profile cb function */
|
||||
gatts_if == heart_rate_profile_tab[idx].gatts_if) {
|
||||
if (heart_rate_profile_tab[idx].gatts_cb) {
|
||||
heart_rate_profile_tab[idx].gatts_cb(event, gatts_if, param);
|
||||
}
|
||||
}
|
||||
}
|
||||
} while (0);
|
||||
}
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
esp_err_t ret;
|
||||
|
||||
// Initialize NVS.
|
||||
ret = nvs_flash_init();
|
||||
if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
|
||||
ESP_ERROR_CHECK(nvs_flash_erase());
|
||||
ret = nvs_flash_init();
|
||||
}
|
||||
ESP_ERROR_CHECK( ret );
|
||||
|
||||
ESP_ERROR_CHECK(esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT));
|
||||
|
||||
esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT();
|
||||
ret = esp_bt_controller_init(&bt_cfg);
|
||||
if (ret) {
|
||||
ESP_LOGE(LOG_TAG, "%s init controller failed: %s", __func__, esp_err_to_name(ret));
|
||||
return;
|
||||
}
|
||||
ret = esp_bt_controller_enable(ESP_BT_MODE_BLE);
|
||||
if (ret) {
|
||||
ESP_LOGE(LOG_TAG, "%s enable controller failed: %s", __func__, esp_err_to_name(ret));
|
||||
return;
|
||||
}
|
||||
|
||||
ESP_LOGI(LOG_TAG, "%s init bluetooth", __func__);
|
||||
|
||||
esp_bluedroid_config_t cfg = BT_BLUEDROID_INIT_CONFIG_DEFAULT();
|
||||
ret = esp_bluedroid_init_with_cfg(&cfg);
|
||||
if (ret) {
|
||||
ESP_LOGE(LOG_TAG, "%s init bluetooth failed: %s", __func__, esp_err_to_name(ret));
|
||||
return;
|
||||
}
|
||||
ret = esp_bluedroid_enable();
|
||||
if (ret) {
|
||||
ESP_LOGE(LOG_TAG, "%s enable bluetooth failed: %s", __func__, esp_err_to_name(ret));
|
||||
return;
|
||||
}
|
||||
|
||||
ret = esp_ble_cte_register_callback(cte_event_handler);
|
||||
if (ret){
|
||||
ESP_LOGE(LOG_TAG, "CTE callback register error, error code = %x", ret);
|
||||
return;
|
||||
}
|
||||
|
||||
ret = esp_ble_gatts_register_callback(gatts_event_handler);
|
||||
if (ret){
|
||||
ESP_LOGE(LOG_TAG, "gatts register error, error code = %x", ret);
|
||||
return;
|
||||
}
|
||||
ret = esp_ble_gap_register_callback(gap_event_handler);
|
||||
if (ret){
|
||||
ESP_LOGE(LOG_TAG, "gap register error, error code = %x", ret);
|
||||
return;
|
||||
}
|
||||
ret = esp_ble_gatts_app_register(ESP_HEART_RATE_APP_ID);
|
||||
if (ret){
|
||||
ESP_LOGE(LOG_TAG, "gatts app register error, error code = %x", ret);
|
||||
return;
|
||||
}
|
||||
|
||||
/* set the security iocap & auth_req & key size & init key response key parameters to the stack*/
|
||||
esp_ble_auth_req_t auth_req = ESP_LE_AUTH_REQ_SC_MITM_BOND; //bonding with peer device after authentication
|
||||
esp_ble_io_cap_t iocap = ESP_IO_CAP_NONE; //set the IO capability to No output No input
|
||||
uint8_t key_size = 16; //the key size should be 7~16 bytes
|
||||
uint8_t init_key = ESP_BLE_ENC_KEY_MASK | ESP_BLE_ID_KEY_MASK;
|
||||
uint8_t rsp_key = ESP_BLE_ENC_KEY_MASK | ESP_BLE_ID_KEY_MASK;
|
||||
//set static passkey
|
||||
uint32_t passkey = 123456;
|
||||
uint8_t auth_option = ESP_BLE_ONLY_ACCEPT_SPECIFIED_AUTH_DISABLE;
|
||||
uint8_t oob_support = ESP_BLE_OOB_DISABLE;
|
||||
esp_ble_gap_set_security_param(ESP_BLE_SM_SET_STATIC_PASSKEY, &passkey, sizeof(uint32_t));
|
||||
esp_ble_gap_set_security_param(ESP_BLE_SM_AUTHEN_REQ_MODE, &auth_req, sizeof(uint8_t));
|
||||
esp_ble_gap_set_security_param(ESP_BLE_SM_IOCAP_MODE, &iocap, sizeof(uint8_t));
|
||||
esp_ble_gap_set_security_param(ESP_BLE_SM_MAX_KEY_SIZE, &key_size, sizeof(uint8_t));
|
||||
esp_ble_gap_set_security_param(ESP_BLE_SM_ONLY_ACCEPT_SPECIFIED_SEC_AUTH, &auth_option, sizeof(uint8_t));
|
||||
esp_ble_gap_set_security_param(ESP_BLE_SM_OOB_SUPPORT, &oob_support, sizeof(uint8_t));
|
||||
/* If your BLE device acts as a Slave, the init_key means you hope which types of key of the master should distribute to you,
|
||||
and the response key means which key you can distribute to the master;
|
||||
If your BLE device acts as a master, the response key means you hope which types of key of the slave should distribute to you,
|
||||
and the init key means which key you can distribute to the slave. */
|
||||
esp_ble_gap_set_security_param(ESP_BLE_SM_SET_INIT_KEY, &init_key, sizeof(uint8_t));
|
||||
esp_ble_gap_set_security_param(ESP_BLE_SM_SET_RSP_KEY, &rsp_key, sizeof(uint8_t));
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Unlicense OR CC0-1.0
|
||||
*/
|
||||
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
|
||||
/* Attributes State Machine */
|
||||
enum
|
||||
{
|
||||
IDX_SVC,
|
||||
IDX_CHAR_A,
|
||||
IDX_CHAR_VAL_A,
|
||||
IDX_CHAR_CFG_A,
|
||||
|
||||
IDX_CHAR_B,
|
||||
IDX_CHAR_VAL_B,
|
||||
|
||||
IDX_CHAR_C,
|
||||
IDX_CHAR_VAL_C,
|
||||
|
||||
HRS_IDX_NB,
|
||||
};
|
||||
@@ -0,0 +1,25 @@
|
||||
# This file was generated using idf.py save-defconfig. It can be edited manually.
|
||||
# Espressif IoT Development Framework (ESP-IDF) Project Minimal Configuration
|
||||
#
|
||||
CONFIG_BT_ENABLED=y
|
||||
CONFIG_BT_BLE_FEAT_CTE_EN=y
|
||||
|
||||
CONFIG_BT_BLE_FEAT_CTE_CONNECTION_EN=y
|
||||
|
||||
CONFIG_BT_BLE_FEAT_CTE_CONNECTIONLESS_EN=n
|
||||
|
||||
CONFIG_BT_BLE_SMP_ENABLE=y
|
||||
|
||||
CONFIG_BT_GATTC_ENABLE=n
|
||||
|
||||
CONFIG_BT_GATTS_ENABLE=y
|
||||
|
||||
CONFIG_BT_BLE_50_EXTEND_ADV_EN=y
|
||||
|
||||
CONFIG_BT_BLE_50_PERIODIC_ADV_EN=n
|
||||
|
||||
CONFIG_BT_BLE_50_EXTEND_SCAN_EN=n
|
||||
|
||||
CONFIG_BT_BLE_50_EXTEND_SYNC_EN=n
|
||||
|
||||
CONFIG_BT_BLE_50_DTM_TEST_EN=n
|
||||
@@ -0,0 +1,9 @@
|
||||
# The following lines of boilerplate have to be in your project's
|
||||
# CMakeLists in this exact order for cmake to work correctly
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
|
||||
# Set project name
|
||||
set(PROJECT_NAME "ble_pawr_advertiser")
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
project(${PROJECT_NAME})
|
||||
@@ -0,0 +1,88 @@
|
||||
| Supported Targets | ESP32-C5 | ESP32-C6 | ESP32-C61 | ESP32-H2 |
|
||||
| ----------------- | -------- | -------- | --------- | -------- |
|
||||
|
||||
# BLE Periodic Advertiser With Response (PAwR) Advertiser Example
|
||||
|
||||
(See the README.md file in the upper level 'examples' directory for more information about examples.)
|
||||
|
||||
This example demonstrates **Periodic Advertising with Responses (PAwR)** on the advertiser side.
|
||||
|
||||
This example starts PAwR advertising with configurable subevents and response slots.
|
||||
|
||||
It uses Bluetooth controller and Bluedroid stack based BLE host.
|
||||
|
||||
This example aims at understanding PAwR advertisement and related Bluedroid APIs.
|
||||
|
||||
## How to Test
|
||||
|
||||
### Pairing Test: Advertiser + Synchronizer
|
||||
|
||||
This example should be paired with [ble_pawr_synchronizer](../ble_pawr_synchronizer) for testing.
|
||||
|
||||
**Test Setup:**
|
||||
1. **Device A (Advertiser)**: Flash `ble_pawr_advertiser` example
|
||||
2. **Device B (Synchronizer)**: Flash `ble_pawr_synchronizer` example
|
||||
|
||||
**Steps:**
|
||||
1. Power on Device A first (advertiser starts broadcasting)
|
||||
2. Power on Device B (synchronizer starts scanning)
|
||||
3. Device B will automatically detect and sync to Device A
|
||||
4. Both devices will exchange PAwR data
|
||||
|
||||
**Expected Behavior:**
|
||||
- Advertiser sends periodic advertising with subevents
|
||||
- Synchronizer receives subevents and sends responses
|
||||
- Advertiser receives response reports
|
||||
|
||||
## 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>
|
||||
```
|
||||
|
||||
### Configure the project
|
||||
|
||||
Open the project configuration menu:
|
||||
|
||||
```bash
|
||||
idf.py menuconfig
|
||||
```
|
||||
|
||||
In the `Example Configuration` menu:
|
||||
|
||||
* Select I/O capabilities of device from `Example Configuration --> I/O Capability`, default is `Just_works`.
|
||||
|
||||
### 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
|
||||
|
||||
```
|
||||
I (504) BLE_PAWR_ADV: Device Address: 40:4c:ca:46:1f:e2
|
||||
I (514) BLE_PAWR_ADV: Extended advertising params set, status 0, instance 0
|
||||
I (524) BLE_PAWR_ADV: Extended advertising data set, status 0, instance 0
|
||||
I (534) BLE_PAWR_ADV: Extended advertising start, status 0, instance num 1
|
||||
I (544) BLE_PAWR_ADV: Periodic advertising params set, status 0, instance 0
|
||||
I (554) BLE_PAWR_ADV: Periodic advertising start, status 0, instance 0
|
||||
I (564) BLE_PAWR_ADV: PAwR advertising started successfully
|
||||
I (574) BLE_PAWR_ADV: BLE PAwR Advertiser example started
|
||||
I (664) BLE_PAWR_ADV: [Request] data: 0x00, subevt start:0, subevt count:5
|
||||
I (814) BLE_PAWR_ADV: [Request] data: 0x05, subevt start:5, subevt count:2
|
||||
I (964) BLE_PAWR_ADV: [Request] data: 0x07, subevt start:7, subevt count:2
|
||||
I (1114) BLE_PAWR_ADV: [Request] data: 0x09, subevt start:9, subevt count:2
|
||||
I (1163054) BLE_PAWR_ADV: [Response] adv_handle:0, subevent:0, tx_status:0, num_rsp:1
|
||||
I (1163054) BLE_PAWR_ADV: Response slot:114, data_length:1
|
||||
I (1163054) BLE_PAWR_ADV: data: 0x8c, 0x0
|
||||
```
|
||||
|
||||
## 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
|
||||
INCLUDE_DIRS ".")
|
||||
@@ -0,0 +1,307 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Unlicense OR CC0-1.0
|
||||
*/
|
||||
|
||||
/****************************************************************************
|
||||
*
|
||||
* This demo showcases BLE Periodic Advertising with Responses (PAwR) on the advertiser side.
|
||||
* The device starts extended advertising and periodic advertising with subevents.
|
||||
* When the host receives ESP_GAP_BLE_PERIODIC_ADV_SUBEVT_DATA_REQUEST_EVT, it provides
|
||||
* per-subevent data via esp_ble_gap_set_periodic_adv_subevent_data().
|
||||
* If a synchronizer sends responses, the advertiser will receive
|
||||
* ESP_GAP_BLE_PERIODIC_ADV_RESPONSE_REPORT_EVT.
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "freertos/semphr.h"
|
||||
#include "esp_system.h"
|
||||
#include "esp_log.h"
|
||||
#include "nvs_flash.h"
|
||||
#include "esp_bt.h"
|
||||
#include "esp_bt_main.h"
|
||||
#include "esp_gap_ble_api.h"
|
||||
#include "esp_bt_defs.h"
|
||||
|
||||
#include "sdkconfig.h"
|
||||
|
||||
#define TAG "BLE_PAWR_ADV"
|
||||
|
||||
#define EXT_ADV_HANDLE 0
|
||||
#define NUM_EXT_ADV 1
|
||||
|
||||
#define SEM_WAIT_TIMEOUT_MS 5000
|
||||
|
||||
#define FUNC_SEND_WAIT_SEM(func, sem) do {\
|
||||
esp_err_t __err_rc = (func);\
|
||||
if (__err_rc != ESP_OK) { \
|
||||
ESP_LOGE(TAG, "%s failed, error = %d", #func, __err_rc); \
|
||||
return; \
|
||||
} \
|
||||
xSemaphoreTake(sem, portMAX_DELAY); \
|
||||
} while(0);
|
||||
|
||||
// PAwR parameters
|
||||
#define BLE_PAWR_SUB_DATA_LEN (20)
|
||||
|
||||
static SemaphoreHandle_t test_sem = NULL;
|
||||
static esp_ble_subevent_params sub_data_params[10];
|
||||
static uint8_t sub_data_pattern[BLE_PAWR_SUB_DATA_LEN] = {0};
|
||||
static uint8_t subevent_data_buffers[10][BLE_PAWR_SUB_DATA_LEN];
|
||||
|
||||
// Extended advertising data
|
||||
static uint8_t raw_ext_adv_data[] = {
|
||||
0x02, ESP_BLE_AD_TYPE_FLAG, 0x06,
|
||||
0x02, ESP_BLE_AD_TYPE_TX_PWR, 0xeb,
|
||||
0x10, ESP_BLE_AD_TYPE_NAME_CMPL, 'E', 'S', 'P', '_', 'E', 'X', 'T', 'P', 'A', 'w', 'R', '_', 'A', 'D', 'V'
|
||||
};
|
||||
|
||||
static esp_ble_gap_ext_adv_params_t ext_adv_params = {
|
||||
.type = ESP_BLE_GAP_SET_EXT_ADV_PROP_NONCONN_NONSCANNABLE_UNDIRECTED,
|
||||
.interval_min = 600,
|
||||
.interval_max = 600,
|
||||
.channel_map = ADV_CHNL_ALL,
|
||||
.filter_policy = ADV_FILTER_ALLOW_SCAN_ANY_CON_ANY,
|
||||
.primary_phy = ESP_BLE_GAP_PHY_1M,
|
||||
.max_skip = 0,
|
||||
.secondary_phy = ESP_BLE_GAP_PHY_2M,
|
||||
.sid = 0,
|
||||
.scan_req_notif = false,
|
||||
.own_addr_type = BLE_ADDR_TYPE_RANDOM,
|
||||
.tx_power = EXT_ADV_TX_PWR_NO_PREFERENCE,
|
||||
};
|
||||
|
||||
static esp_ble_gap_periodic_adv_params_t periodic_adv_params = {
|
||||
.interval_min = 600,
|
||||
.interval_max = 600,
|
||||
.properties = 0, // Do not include TX power
|
||||
.num_subevents = 10,
|
||||
.subevent_interval = 44,
|
||||
.rsp_slot_delay = 20,
|
||||
.rsp_slot_spacing = 32,
|
||||
.num_rsp_slots = 5,
|
||||
};
|
||||
|
||||
static esp_ble_gap_ext_adv_t ext_adv[1] = {
|
||||
[0] = {EXT_ADV_HANDLE, 0, 0},
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief GAP event handler
|
||||
*/
|
||||
static void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param)
|
||||
{
|
||||
if (param == NULL) {
|
||||
ESP_LOGE(TAG, "GAP event handler received NULL param");
|
||||
return;
|
||||
}
|
||||
|
||||
switch (event) {
|
||||
case ESP_GAP_BLE_EXT_ADV_SET_PARAMS_COMPLETE_EVT:
|
||||
xSemaphoreGive(test_sem);
|
||||
ESP_LOGI(TAG, "Extended advertising params set, status %d, instance %d",
|
||||
param->ext_adv_set_params.status, param->ext_adv_set_params.instance);
|
||||
break;
|
||||
case ESP_GAP_BLE_EXT_ADV_DATA_SET_COMPLETE_EVT:
|
||||
xSemaphoreGive(test_sem);
|
||||
ESP_LOGI(TAG, "Extended advertising data set, status %d, instance %d",
|
||||
param->ext_adv_data_set.status, param->ext_adv_data_set.instance);
|
||||
break;
|
||||
case ESP_GAP_BLE_EXT_ADV_START_COMPLETE_EVT:
|
||||
xSemaphoreGive(test_sem);
|
||||
ESP_LOGI(TAG, "Extended advertising start, status %d, instance num %d",
|
||||
param->ext_adv_start.status, param->ext_adv_start.instance_num);
|
||||
break;
|
||||
case ESP_GAP_BLE_EXT_ADV_SET_RAND_ADDR_COMPLETE_EVT:
|
||||
xSemaphoreGive(test_sem);
|
||||
ESP_LOGI(TAG, "Extended advertising set random address, status %d, instance %d",
|
||||
param->ext_adv_set_rand_addr.status, param->ext_adv_set_rand_addr.instance);
|
||||
break;
|
||||
case ESP_GAP_BLE_PERIODIC_ADV_SET_PARAMS_COMPLETE_EVT:
|
||||
xSemaphoreGive(test_sem);
|
||||
ESP_LOGI(TAG, "Periodic advertising params set, status %d, instance %d",
|
||||
param->peroid_adv_set_params.status, param->peroid_adv_set_params.instance);
|
||||
break;
|
||||
case ESP_GAP_BLE_PERIODIC_ADV_DATA_SET_COMPLETE_EVT:
|
||||
xSemaphoreGive(test_sem);
|
||||
ESP_LOGI(TAG, "Periodic advertising data set, status %d, instance %d",
|
||||
param->period_adv_data_set.status, param->period_adv_data_set.instance);
|
||||
break;
|
||||
case ESP_GAP_BLE_PERIODIC_ADV_START_COMPLETE_EVT:
|
||||
xSemaphoreGive(test_sem);
|
||||
ESP_LOGI(TAG, "Periodic advertising start, status %d, instance %d",
|
||||
param->period_adv_start.status, param->period_adv_start.instance);
|
||||
break;
|
||||
case ESP_GAP_BLE_SET_PERIODIC_ADV_SUBEVT_DATA_EVT:
|
||||
ESP_LOGI(TAG, "Periodic adv subevent data set, status %d, adv_handle %d",
|
||||
param->pa_subevt_data_evt.status, param->pa_subevt_data_evt.adv_handle);
|
||||
break;
|
||||
case ESP_GAP_BLE_PERIODIC_ADV_SUBEVT_DATA_REQUEST_EVT: {
|
||||
ESP_LOGI(TAG, "[Request] data: 0x%02x, subevt start:%d, subevt count:%d",
|
||||
sub_data_pattern[0],
|
||||
param->pa_subevt_data_req_evt.subevt_start,
|
||||
param->pa_subevt_data_req_evt.subevt_data_count);
|
||||
|
||||
uint8_t sent_num = param->pa_subevt_data_req_evt.subevt_data_count;
|
||||
if (sent_num > 10) {
|
||||
ESP_LOGE(TAG, "Invalid subevent count: %d", sent_num);
|
||||
sent_num = 10;
|
||||
}
|
||||
|
||||
// Prepare subevent data
|
||||
for (uint8_t i = 0; i < sent_num && i < 10; i++) {
|
||||
uint8_t sub = (i + param->pa_subevt_data_req_evt.subevt_start) % 10;
|
||||
|
||||
// Prepare data pattern with overflow protection
|
||||
sub_data_pattern[0] = (sub_data_pattern[0] + 1) % 256;
|
||||
memset(&sub_data_pattern[1], sub, BLE_PAWR_SUB_DATA_LEN - 1);
|
||||
|
||||
// Copy to buffer
|
||||
memcpy(subevent_data_buffers[i], sub_data_pattern, BLE_PAWR_SUB_DATA_LEN);
|
||||
|
||||
// Set subevent parameters
|
||||
sub_data_params[i].subevent = sub;
|
||||
sub_data_params[i].response_slot_start = 0;
|
||||
sub_data_params[i].response_slot_count = 5;
|
||||
sub_data_params[i].subevent_data_len = BLE_PAWR_SUB_DATA_LEN;
|
||||
sub_data_params[i].subevent_data = subevent_data_buffers[i];
|
||||
}
|
||||
|
||||
// Set subevent data
|
||||
esp_ble_per_adv_subevent_data_params subevent_data_params = {0};
|
||||
subevent_data_params.adv_handle = param->pa_subevt_data_req_evt.adv_handle;
|
||||
subevent_data_params.num_subevents_with_data = sent_num;
|
||||
subevent_data_params.subevent_params = sub_data_params;
|
||||
|
||||
esp_err_t ret = esp_ble_gap_set_periodic_adv_subevent_data(&subevent_data_params);
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to set Subevent Data, error = 0x%x", ret);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ESP_GAP_BLE_PERIODIC_ADV_RESPONSE_REPORT_EVT: {
|
||||
ESP_LOGI(TAG, "[Response] adv_handle:%d, subevent:%d, tx_status:%d, num_rsp:%d",
|
||||
param->pa_rsp_rpt_evt.adv_handle,
|
||||
param->pa_rsp_rpt_evt.subevt,
|
||||
param->pa_rsp_rpt_evt.tx_status,
|
||||
param->pa_rsp_rpt_evt.num_rsp);
|
||||
|
||||
if (param->pa_rsp_rpt_evt.pa_rsp_info && param->pa_rsp_rpt_evt.num_rsp > 0) {
|
||||
// Limit num_rsp to prevent array overflow
|
||||
uint8_t num_rsp = (param->pa_rsp_rpt_evt.num_rsp > 10) ? 10 : param->pa_rsp_rpt_evt.num_rsp;
|
||||
for (uint8_t i = 0; i < num_rsp && i < 10; i++) {
|
||||
esp_ble_pa_rsp_info *rsp_info = ¶m->pa_rsp_rpt_evt.pa_rsp_info[i];
|
||||
if (rsp_info->data_status == 0x00) { // Data complete
|
||||
ESP_LOGI(TAG, "Response slot:%d, data_length:%d",
|
||||
rsp_info->rsp_slot, rsp_info->data_len);
|
||||
if (rsp_info->data && rsp_info->data_len >= 2) {
|
||||
ESP_LOGI(TAG, "data: 0x%02x, 0x%02x", rsp_info->data[0], rsp_info->data[1]);
|
||||
}
|
||||
} else {
|
||||
ESP_LOGW(TAG, "Response slot:%d, data_status:%d",
|
||||
rsp_info->rsp_slot, rsp_info->data_status);
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Start periodic advertising with PAwR
|
||||
*/
|
||||
static void start_periodic_adv(void)
|
||||
{
|
||||
// Create static random address
|
||||
esp_bd_addr_t rand_addr;
|
||||
esp_ble_gap_addr_create_static(rand_addr);
|
||||
|
||||
ESP_LOG_BUFFER_HEX(TAG, rand_addr, ESP_BD_ADDR_LEN);
|
||||
|
||||
// Set extended advertising parameters
|
||||
ESP_LOGI(TAG, "Set extended advertising params...");
|
||||
FUNC_SEND_WAIT_SEM(esp_ble_gap_ext_adv_set_params(EXT_ADV_HANDLE, &ext_adv_params), test_sem);
|
||||
FUNC_SEND_WAIT_SEM(esp_ble_gap_ext_adv_set_rand_addr(EXT_ADV_HANDLE, rand_addr), test_sem);
|
||||
|
||||
// Set extended advertising data
|
||||
ESP_LOGI(TAG, "Set extended advertising data...");
|
||||
FUNC_SEND_WAIT_SEM(esp_ble_gap_config_ext_adv_data_raw(EXT_ADV_HANDLE,
|
||||
sizeof(raw_ext_adv_data),
|
||||
raw_ext_adv_data), test_sem);
|
||||
|
||||
// Start extended advertising
|
||||
ESP_LOGI(TAG, "Start extended advertising...");
|
||||
FUNC_SEND_WAIT_SEM(esp_ble_gap_ext_adv_start(NUM_EXT_ADV, ext_adv), test_sem);
|
||||
|
||||
// Set periodic advertising parameters
|
||||
ESP_LOGI(TAG, "Set periodic adv (PAwR) params...");
|
||||
FUNC_SEND_WAIT_SEM(esp_ble_gap_periodic_adv_set_params(EXT_ADV_HANDLE, &periodic_adv_params), test_sem);
|
||||
|
||||
// Start periodic advertising (PAwR)
|
||||
ESP_LOGI(TAG, "Start periodic advertising (PAwR)...");
|
||||
FUNC_SEND_WAIT_SEM(esp_ble_gap_periodic_adv_start(EXT_ADV_HANDLE), test_sem);
|
||||
|
||||
ESP_LOGI(TAG, "PAwR advertising started successfully");
|
||||
}
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
esp_err_t ret;
|
||||
|
||||
// Initialize NVS
|
||||
ret = nvs_flash_init();
|
||||
if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
|
||||
ESP_ERROR_CHECK(nvs_flash_erase());
|
||||
ret = nvs_flash_init();
|
||||
}
|
||||
ESP_ERROR_CHECK(ret);
|
||||
|
||||
ESP_ERROR_CHECK(esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT));
|
||||
|
||||
esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT();
|
||||
ret = esp_bt_controller_init(&bt_cfg);
|
||||
if (ret) {
|
||||
ESP_LOGE(TAG, "%s initialize controller failed: %s", __func__, esp_err_to_name(ret));
|
||||
return;
|
||||
}
|
||||
|
||||
ret = esp_bt_controller_enable(ESP_BT_MODE_BLE);
|
||||
if (ret) {
|
||||
ESP_LOGE(TAG, "%s enable controller failed: %s", __func__, esp_err_to_name(ret));
|
||||
return;
|
||||
}
|
||||
|
||||
esp_bluedroid_config_t cfg = BT_BLUEDROID_INIT_CONFIG_DEFAULT();
|
||||
ret = esp_bluedroid_init_with_cfg(&cfg);
|
||||
if (ret) {
|
||||
ESP_LOGE(TAG, "%s init bluetooth failed: %s", __func__, esp_err_to_name(ret));
|
||||
return;
|
||||
}
|
||||
ret = esp_bluedroid_enable();
|
||||
if (ret) {
|
||||
ESP_LOGE(TAG, "%s enable bluetooth failed: %s", __func__, esp_err_to_name(ret));
|
||||
return;
|
||||
}
|
||||
ret = esp_ble_gap_register_callback(gap_event_handler);
|
||||
if (ret){
|
||||
ESP_LOGE(TAG, "gap register error, error code = %x", ret);
|
||||
return;
|
||||
}
|
||||
|
||||
// Create semaphore
|
||||
test_sem = xSemaphoreCreateBinary();
|
||||
|
||||
// Start periodic advertising with PAwR
|
||||
start_periodic_adv();
|
||||
|
||||
ESP_LOGI(TAG, "BLE PAwR Advertiser example started");
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
# This file was generated using idf.py save-defconfig. It can be edited manually.
|
||||
# Espressif IoT Development Framework (ESP-IDF) Project Minimal Configuration
|
||||
#
|
||||
# BT config - Enable Bluetooth
|
||||
CONFIG_BT_ENABLED=y
|
||||
|
||||
# Enable Bluedroid host
|
||||
CONFIG_BT_BLUEDROID_ENABLED=y
|
||||
|
||||
# Enable BLE 5.0 features (required for PAwR)
|
||||
CONFIG_BT_BLE_50_FEATURES_SUPPORTED=y
|
||||
|
||||
# Enable BLE extend advertising (required for periodic advertising)
|
||||
CONFIG_BT_BLE_50_EXTEND_ADV_EN=y
|
||||
|
||||
# Enable BLE periodic advertising
|
||||
CONFIG_BT_BLE_50_PERIODIC_ADV_EN=y
|
||||
|
||||
# Enable periodic advertising enhancements (ADI support)
|
||||
CONFIG_BT_BLE_FEAT_PERIODIC_ADV_ENH=n
|
||||
|
||||
# Enable PAwR feature (Host)
|
||||
CONFIG_BT_BLE_FEAT_PAWR_EN=y
|
||||
|
||||
# Enable PAwR feature (Controller)
|
||||
CONFIG_BT_LE_PERIODIC_ADV_WITH_RESPONSE_ENABLED=y
|
||||
|
||||
CONFIG_BT_BLE_SMP_ENABLE=n
|
||||
|
||||
CONFIG_BT_GATTC_ENABLE=n
|
||||
|
||||
CONFIG_BT_GATTS_ENABLE=n
|
||||
|
||||
CONFIG_BT_BLE_50_EXTEND_SCAN_EN=n
|
||||
|
||||
CONFIG_BT_BLE_50_DTM_TEST_EN=n
|
||||
@@ -0,0 +1,9 @@
|
||||
# The following lines of boilerplate have to be in your project's
|
||||
# CMakeLists in this exact order for cmake to work correctly
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
|
||||
# Set project name
|
||||
set(PROJECT_NAME "ble_pawr_advertiser_conn")
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
project(${PROJECT_NAME})
|
||||
@@ -0,0 +1,127 @@
|
||||
| Supported Targets | ESP32-C5 | ESP32-C6 | ESP32-C61 | ESP32-H2 |
|
||||
| ----------------- | -------- | -------- | --------- | -------- |
|
||||
|
||||
# BLE Periodic Advertiser With Response (PAwR) Advertiser Connection Example
|
||||
|
||||
(See the README.md file in the upper level 'examples' directory for more information about examples.)
|
||||
|
||||
This example demonstrates PAwR connection functionality as PAwR advertiser.
|
||||
|
||||
It uses Bluetooth controller and Bluedroid stack based BLE host.
|
||||
|
||||
This example aims at understanding how to establish connections using PAwR synchronization and related Bluedroid APIs.
|
||||
|
||||
## How to Test
|
||||
|
||||
### Pairing Test: Advertiser with Connection + Synchronizer
|
||||
|
||||
This example should be paired with [ble_pawr_synchronizer](../ble_pawr_synchronizer) for testing.
|
||||
|
||||
**Test Setup:**
|
||||
1. **Device A (Advertiser with Connection)**: Flash `ble_pawr_advertiser_conn` example
|
||||
2. **Device B (Synchronizer)**: Flash `ble_pawr_synchronizer` example
|
||||
|
||||
**Steps:**
|
||||
1. Build and flash advertiser example to Device A:
|
||||
```bash
|
||||
cd examples/bluetooth/bluedroid/ble_50/ble_pawr_advertiser_conn
|
||||
idf.py set-target <chip_name>
|
||||
idf.py -p <PORT_A> build flash monitor
|
||||
```
|
||||
|
||||
2. Build and flash synchronizer example to Device B:
|
||||
```bash
|
||||
cd examples/bluetooth/bluedroid/ble_50/ble_pawr_synchronizer
|
||||
idf.py set-target <chip_name>
|
||||
idf.py -p <PORT_B> build flash monitor
|
||||
```
|
||||
|
||||
3. **Power on Device A first** (advertiser starts broadcasting)
|
||||
4. **Power on Device B** (synchronizer starts scanning and syncing)
|
||||
5. Device B sends responses containing connection information
|
||||
6. Device A receives responses and extracts peer address
|
||||
7. Device A establishes connection using `esp_ble_gattc_aux_open_with_pawr_synced()`
|
||||
|
||||
**Expected Behavior:**
|
||||
- Advertiser sends periodic advertising with subevents
|
||||
- Synchronizer receives subevents and sends responses containing its BLE address
|
||||
- Advertiser receives response reports with connection data
|
||||
- Advertiser establishes BLE connection to synchronizer using `esp_ble_gattc_aux_open_with_pawr_synced()`
|
||||
- Advertiser automatically discovers GATT services and characteristics
|
||||
- Advertiser configures notify to receive data from synchronizer
|
||||
|
||||
**Response Data Format:**
|
||||
The synchronizer sends response data with the following format:
|
||||
- `data[0]` = 0x00
|
||||
- `data[1]` = 0x02
|
||||
- `data[2-7]` = Synchronizer's BLE address (6 bytes, high to low: addr[5], addr[4], addr[3], addr[2], addr[1], addr[0])
|
||||
- `data[8+]` = Additional data (optional)
|
||||
|
||||
**GATT Service:**
|
||||
The synchronizer provides a GATT service (UUID: 0x00FF) with:
|
||||
- Characteristic UUID: 0xFF01
|
||||
- Properties: Read, Write, Notify
|
||||
- CCCD for notify configuration
|
||||
|
||||
## 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>
|
||||
```
|
||||
|
||||
### Configure the project
|
||||
|
||||
Open the project configuration menu:
|
||||
|
||||
```bash
|
||||
idf.py menuconfig
|
||||
```
|
||||
|
||||
In the `Example Configuration` menu:
|
||||
|
||||
* Select I/O capabilities of device from `Example Configuration --> I/O Capability`, default is `Just_works`.
|
||||
|
||||
### 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
|
||||
|
||||
```
|
||||
I (562) BLE_PAWR_ADV_CONN: Device Address: 40:4c:ca:46:1f:e2
|
||||
I (572) BLE_PAWR_ADV_CONN: Extended advertising params set, status 0, instance 0
|
||||
I (582) BLE_PAWR_ADV_CONN: Extended advertising data set, status 0, instance 0
|
||||
I (592) BLE_PAWR_ADV_CONN: Extended advertising start, status 0, instance num 1
|
||||
I (602) BLE_PAWR_ADV_CONN: Periodic advertising params set, status 0, instance 0
|
||||
I (612) BLE_PAWR_ADV_CONN: Periodic advertising start, status 0, instance 0
|
||||
I (622) BLE_PAWR_ADV_CONN: PAwR advertising started successfully
|
||||
I (632) BLE_PAWR_ADV_CONN: BLE PAwR Advertiser Connection example started
|
||||
I (642) BLE_PAWR_ADV_CONN: [Request] data: 0x00, subevt start:0, subevt count:5
|
||||
I (1842) BLE_PAWR_ADV_CONN: [Request] data: 0x17, subevt start:3, subevt count:2
|
||||
I (1872) BLE_PAWR_ADV_CONN: [Response] adv_handle:0, subevent:0, tx_status:0, num_rsp:1
|
||||
I (1872) BLE_PAWR_ADV_CONN: Response slot:2, data_length:10
|
||||
I (1872) BLE_PAWR_ADV_CONN: data: 0x00, 0x02, 0x5e, 0x02, 0xf6, 0xf9, 0x55, 0x60, 0x00, 0x00
|
||||
I (1882) BLE_PAWR_ADV_CONN: Connection create sent, adv handle = 0, subevent = 0
|
||||
I (2192) BLE_PAWR_ADV_CONN: [Connection established], conn_id = 0x0000, status = 0x00
|
||||
I (2202) BLE_PAWR_ADV_CONN: Connection Information:
|
||||
I (2202) BLE_PAWR_ADV_CONN: Connection ID: 0x0000
|
||||
I (2212) BLE_PAWR_ADV_CONN: Remote Address: 60:55:f9:f6:02:5e
|
||||
I (2222) BLE_PAWR_ADV_CONN: GATTC open successfully, conn_id = 0x0000, MTU 23
|
||||
I (2232) BLE_PAWR_ADV_CONN: Service discover complete, conn_id 0
|
||||
I (2242) BLE_PAWR_ADV_CONN: Service search result, conn_id = 0x0000, is_primary 1
|
||||
I (2252) BLE_PAWR_ADV_CONN: PAwR Sync Service found
|
||||
I (2262) BLE_PAWR_ADV_CONN: Characteristic found, handle = X, properties = 0xXX
|
||||
I (2272) BLE_PAWR_ADV_CONN: Notification register successfully, handle = X
|
||||
I (2282) BLE_PAWR_ADV_CONN: Descriptor write successfully, notify enabled
|
||||
```
|
||||
|
||||
## 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
|
||||
INCLUDE_DIRS ".")
|
||||
@@ -0,0 +1,690 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Unlicense OR CC0-1.0
|
||||
*/
|
||||
|
||||
/****************************************************************************
|
||||
*
|
||||
* This demo showcases BLE Periodic Advertising with Responses (PAwR) on the advertiser side
|
||||
* with connection capability. The device starts extended advertising and periodic advertising
|
||||
* with subevents. When the host receives ESP_GAP_BLE_PERIODIC_ADV_SUBEVT_DATA_REQUEST_EVT,
|
||||
* it provides per-subevent data via esp_ble_gap_set_periodic_adv_subevent_data().
|
||||
* If a synchronizer sends responses containing connection information, the advertiser will
|
||||
* receive ESP_GAP_BLE_PERIODIC_ADV_RESPONSE_REPORT_EVT and can establish a connection using
|
||||
* esp_ble_gattc_aux_open_with_pawr_synced().
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "freertos/semphr.h"
|
||||
#include "esp_system.h"
|
||||
#include "esp_log.h"
|
||||
#include "nvs_flash.h"
|
||||
#include "esp_bt.h"
|
||||
#include "esp_bt_main.h"
|
||||
#include "esp_gap_ble_api.h"
|
||||
#include "esp_gattc_api.h"
|
||||
#include "esp_gatt_defs.h"
|
||||
#include "esp_bt_defs.h"
|
||||
|
||||
#include "sdkconfig.h"
|
||||
|
||||
#define TAG "BLE_PAWR_ADV_CONN"
|
||||
|
||||
#define EXT_ADV_HANDLE 0
|
||||
#define NUM_EXT_ADV 1
|
||||
#define PROFILE_A_APP_ID 0
|
||||
|
||||
#define SEM_WAIT_TIMEOUT_MS 5000
|
||||
|
||||
/* Service UUID and Characteristic UUID (matching synchronizer) */
|
||||
#define GATTS_SERVICE_UUID_PAWR_SYNC 0x00FF
|
||||
#define GATTS_CHAR_UUID_PAWR_SYNC 0xFF01
|
||||
#define INVALID_HANDLE 0xFFFF
|
||||
|
||||
#define FUNC_SEND_WAIT_SEM(func, sem) do {\
|
||||
esp_err_t __err_rc = (func);\
|
||||
if (__err_rc != ESP_OK) { \
|
||||
ESP_LOGE(TAG, "%s failed, error = %d", #func, __err_rc); \
|
||||
return; \
|
||||
} \
|
||||
if (xSemaphoreTake(sem, pdMS_TO_TICKS(SEM_WAIT_TIMEOUT_MS)) != pdTRUE) { \
|
||||
ESP_LOGE(TAG, "%s, semaphore timeout", __func__); \
|
||||
return; \
|
||||
} \
|
||||
} while(0);
|
||||
|
||||
// PAwR parameters
|
||||
#define BLE_PAWR_SUB_DATA_LEN (20)
|
||||
|
||||
static SemaphoreHandle_t test_sem = NULL;
|
||||
static esp_ble_subevent_params sub_data_params[10];
|
||||
static uint8_t sub_data_pattern[BLE_PAWR_SUB_DATA_LEN] = {0};
|
||||
static uint8_t subevent_data_buffers[10][BLE_PAWR_SUB_DATA_LEN];
|
||||
static bool is_connected = false;
|
||||
static uint16_t conn_id = 0xFFFF;
|
||||
|
||||
// Connection parameters
|
||||
static esp_ble_conn_params_t conn_params = {
|
||||
.interval_min = 0x20, // 40 * 1.25ms = 50ms
|
||||
.interval_max = 0x20, // 40 * 1.25ms = 50ms
|
||||
.latency = 0,
|
||||
.supervision_timeout = 0x1F4, // 500 * 10ms = 5000ms
|
||||
};
|
||||
|
||||
// Extended advertising data
|
||||
static uint8_t raw_ext_adv_data[] = {
|
||||
0x02, ESP_BLE_AD_TYPE_FLAG, 0x06,
|
||||
0x02, ESP_BLE_AD_TYPE_TX_PWR, 0xeb,
|
||||
0x10, ESP_BLE_AD_TYPE_NAME_CMPL, 'E', 'S', 'P', '_', 'E', 'X', 'T', 'P', 'A', 'w', 'R', '_', 'A', 'D', 'V'
|
||||
};
|
||||
|
||||
static esp_ble_gap_ext_adv_params_t ext_adv_params = {
|
||||
.type = ESP_BLE_GAP_SET_EXT_ADV_PROP_NONCONN_NONSCANNABLE_UNDIRECTED,
|
||||
.interval_min = 600,
|
||||
.interval_max = 600,
|
||||
.channel_map = ADV_CHNL_ALL,
|
||||
.filter_policy = ADV_FILTER_ALLOW_SCAN_ANY_CON_ANY,
|
||||
.primary_phy = ESP_BLE_GAP_PHY_1M,
|
||||
.max_skip = 0,
|
||||
.secondary_phy = ESP_BLE_GAP_PHY_2M,
|
||||
.sid = 0,
|
||||
.scan_req_notif = false,
|
||||
.own_addr_type = BLE_ADDR_TYPE_RANDOM,
|
||||
.tx_power = EXT_ADV_TX_PWR_NO_PREFERENCE,
|
||||
};
|
||||
|
||||
static esp_ble_gap_periodic_adv_params_t periodic_adv_params = {
|
||||
.interval_min = 600,
|
||||
.interval_max = 600,
|
||||
.properties = 0, // Do not include TX power
|
||||
.num_subevents = 10,
|
||||
.subevent_interval = 44,
|
||||
.rsp_slot_delay = 20,
|
||||
.rsp_slot_spacing = 32,
|
||||
.num_rsp_slots = 5,
|
||||
};
|
||||
|
||||
static esp_ble_gap_ext_adv_t ext_adv[1] = {
|
||||
[0] = {EXT_ADV_HANDLE, 0, 0},
|
||||
};
|
||||
|
||||
// Forward declaration
|
||||
static void gattc_profile_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param);
|
||||
|
||||
static struct gattc_profile_inst {
|
||||
esp_gattc_cb_t gattc_cb;
|
||||
uint16_t gattc_if;
|
||||
uint16_t app_id;
|
||||
uint16_t conn_id;
|
||||
esp_bd_addr_t remote_bda;
|
||||
uint16_t service_start_handle;
|
||||
uint16_t service_end_handle;
|
||||
uint16_t char_handle;
|
||||
} gl_profile_tab[1] = {
|
||||
[PROFILE_A_APP_ID] = {
|
||||
.gattc_cb = gattc_profile_event_handler,
|
||||
.gattc_if = ESP_GATT_IF_NONE,
|
||||
.service_start_handle = INVALID_HANDLE,
|
||||
.service_end_handle = INVALID_HANDLE,
|
||||
.char_handle = INVALID_HANDLE,
|
||||
},
|
||||
};
|
||||
|
||||
/* UUID for service discovery */
|
||||
static esp_bt_uuid_t remote_filter_service_uuid = {
|
||||
.len = ESP_UUID_LEN_16,
|
||||
.uuid = {.uuid16 = GATTS_SERVICE_UUID_PAWR_SYNC},
|
||||
};
|
||||
|
||||
static esp_bt_uuid_t remote_filter_char_uuid = {
|
||||
.len = ESP_UUID_LEN_16,
|
||||
.uuid = {.uuid16 = GATTS_CHAR_UUID_PAWR_SYNC},
|
||||
};
|
||||
|
||||
static esp_bt_uuid_t notify_descr_uuid = {
|
||||
.len = ESP_UUID_LEN_16,
|
||||
.uuid = {.uuid16 = ESP_GATT_UUID_CHAR_CLIENT_CONFIG},
|
||||
};
|
||||
|
||||
static bool get_server = false;
|
||||
|
||||
/**
|
||||
* @brief Print connection information
|
||||
*/
|
||||
static void print_conn_info(uint16_t conn_id, esp_bd_addr_t remote_bda)
|
||||
{
|
||||
ESP_LOGI(TAG, "Connection Information:");
|
||||
ESP_LOGI(TAG, " Connection ID: 0x%04x", conn_id);
|
||||
ESP_LOGI(TAG, " Remote Address: "ESP_BD_ADDR_STR"", ESP_BD_ADDR_HEX(remote_bda));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief GAP event handler
|
||||
*/
|
||||
static void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param)
|
||||
{
|
||||
if (param == NULL) {
|
||||
ESP_LOGE(TAG, "GAP event handler received NULL param");
|
||||
return;
|
||||
}
|
||||
|
||||
switch (event) {
|
||||
case ESP_GAP_BLE_EXT_ADV_SET_PARAMS_COMPLETE_EVT:
|
||||
xSemaphoreGive(test_sem);
|
||||
ESP_LOGI(TAG, "Extended advertising params set, status %d, instance %d",
|
||||
param->ext_adv_set_params.status, param->ext_adv_set_params.instance);
|
||||
break;
|
||||
case ESP_GAP_BLE_EXT_ADV_DATA_SET_COMPLETE_EVT:
|
||||
xSemaphoreGive(test_sem);
|
||||
ESP_LOGI(TAG, "Extended advertising data set, status %d, instance %d",
|
||||
param->ext_adv_data_set.status, param->ext_adv_data_set.instance);
|
||||
break;
|
||||
case ESP_GAP_BLE_EXT_ADV_START_COMPLETE_EVT:
|
||||
xSemaphoreGive(test_sem);
|
||||
ESP_LOGI(TAG, "Extended advertising start, status %d, instance num %d",
|
||||
param->ext_adv_start.status, param->ext_adv_start.instance_num);
|
||||
break;
|
||||
case ESP_GAP_BLE_EXT_ADV_SET_RAND_ADDR_COMPLETE_EVT:
|
||||
xSemaphoreGive(test_sem);
|
||||
ESP_LOGI(TAG, "Extended advertising set random address complete, status %d, instance %d",
|
||||
param->ext_adv_set_rand_addr.status, param->ext_adv_set_rand_addr.instance);
|
||||
break;
|
||||
case ESP_GAP_BLE_PERIODIC_ADV_SET_PARAMS_COMPLETE_EVT:
|
||||
xSemaphoreGive(test_sem);
|
||||
ESP_LOGI(TAG, "Periodic advertising params set, status %d, instance %d",
|
||||
param->peroid_adv_set_params.status, param->peroid_adv_set_params.instance);
|
||||
break;
|
||||
case ESP_GAP_BLE_PERIODIC_ADV_START_COMPLETE_EVT:
|
||||
xSemaphoreGive(test_sem);
|
||||
ESP_LOGI(TAG, "Periodic advertising start, status %d, instance %d",
|
||||
param->period_adv_start.status, param->period_adv_start.instance);
|
||||
break;
|
||||
case ESP_GAP_BLE_SET_PERIODIC_ADV_SUBEVT_DATA_EVT:
|
||||
if (param->pa_subevt_data_evt.status != ESP_OK) {
|
||||
if (param->pa_subevt_data_evt.status == 0x146)
|
||||
{
|
||||
ESP_LOGW(TAG, "Periodic adv subevent data set too late, status %d, adv_handle %d",
|
||||
param->pa_subevt_data_evt.status, param->pa_subevt_data_evt.adv_handle);
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Periodic adv subevent data set failed, status %d, adv_handle %d",
|
||||
param->pa_subevt_data_evt.status, param->pa_subevt_data_evt.adv_handle);
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case ESP_GAP_BLE_PERIODIC_ADV_SUBEVT_DATA_REQUEST_EVT: {
|
||||
// ESP_LOGI(TAG, "[Request] data: 0x%02x, subevt start:%d, subevt count:%d",
|
||||
// sub_data_pattern[0],
|
||||
// param->pa_subevt_data_req_evt.subevt_start,
|
||||
// param->pa_subevt_data_req_evt.subevt_data_count);
|
||||
|
||||
uint8_t sent_num = param->pa_subevt_data_req_evt.subevt_data_count;
|
||||
if (sent_num > 10) {
|
||||
ESP_LOGE(TAG, "Invalid subevent count: %d", sent_num);
|
||||
sent_num = 10;
|
||||
}
|
||||
|
||||
// Prepare subevent data
|
||||
for (uint8_t i = 0; i < sent_num && i < 10; i++) {
|
||||
uint8_t sub = (i + param->pa_subevt_data_req_evt.subevt_start) % 10;
|
||||
|
||||
// Prepare data pattern with overflow protection
|
||||
sub_data_pattern[0] = (sub_data_pattern[0] + 1) % 256;
|
||||
memset(&sub_data_pattern[1], sub, BLE_PAWR_SUB_DATA_LEN - 1);
|
||||
|
||||
// Copy to buffer
|
||||
memcpy(subevent_data_buffers[i], sub_data_pattern, BLE_PAWR_SUB_DATA_LEN);
|
||||
|
||||
// Set subevent parameters
|
||||
sub_data_params[i].subevent = sub;
|
||||
sub_data_params[i].response_slot_start = 0;
|
||||
sub_data_params[i].response_slot_count = 5;
|
||||
sub_data_params[i].subevent_data_len = BLE_PAWR_SUB_DATA_LEN;
|
||||
sub_data_params[i].subevent_data = subevent_data_buffers[i];
|
||||
}
|
||||
|
||||
// Set subevent data
|
||||
esp_ble_per_adv_subevent_data_params subevent_data_params = {0};
|
||||
subevent_data_params.adv_handle = param->pa_subevt_data_req_evt.adv_handle;
|
||||
subevent_data_params.num_subevents_with_data = sent_num;
|
||||
subevent_data_params.subevent_params = sub_data_params;
|
||||
|
||||
esp_err_t ret = esp_ble_gap_set_periodic_adv_subevent_data(&subevent_data_params);
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to set Subevent Data, error = 0x%x", ret);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ESP_GAP_BLE_PERIODIC_ADV_RESPONSE_REPORT_EVT: {
|
||||
// ESP_LOGI(TAG, "[Response] adv_handle:%d, subevent:%d, tx_status:%d, num_rsp:%d",
|
||||
// param->pa_rsp_rpt_evt.adv_handle,
|
||||
// param->pa_rsp_rpt_evt.subevt,
|
||||
// param->pa_rsp_rpt_evt.tx_status,
|
||||
// param->pa_rsp_rpt_evt.num_rsp);
|
||||
|
||||
if (param->pa_rsp_rpt_evt.pa_rsp_info && param->pa_rsp_rpt_evt.num_rsp > 0) {
|
||||
// Limit num_rsp to prevent array overflow
|
||||
uint8_t num_rsp = (param->pa_rsp_rpt_evt.num_rsp > 10) ? 10 : param->pa_rsp_rpt_evt.num_rsp;
|
||||
for (uint8_t i = 0; i < num_rsp && i < 10; i++) {
|
||||
esp_ble_pa_rsp_info *rsp_info = ¶m->pa_rsp_rpt_evt.pa_rsp_info[i];
|
||||
if (rsp_info->data_status == 0x00) { // Data complete
|
||||
|
||||
if (rsp_info->data && rsp_info->data_len >= 10) {
|
||||
// Extract peer address from response data (assuming format: [0x00, 0x02, addr[5], addr[4], addr[3], addr[2], addr[1], addr[0], ...])
|
||||
// Need at least 8 bytes: [0x00, 0x02, addr[5], addr[4], addr[3], addr[2], addr[1], addr[0]]
|
||||
// Already validated data_len >= 10 above
|
||||
if (!is_connected && gl_profile_tab[PROFILE_A_APP_ID].gattc_if != ESP_GATT_IF_NONE) {
|
||||
ESP_LOGI(TAG, "Response slot:%d, data_length:%d", rsp_info->rsp_slot, rsp_info->data_len);
|
||||
ESP_LOGI(TAG, "data: 0x%02x, 0x%02x, 0x%02x, 0x%02x, 0x%02x, 0x%02x, 0x%02x, 0x%02x, 0x%02x, 0x%02x",
|
||||
rsp_info->data[0], rsp_info->data[1], rsp_info->data[2], rsp_info->data[3],
|
||||
rsp_info->data[4], rsp_info->data[5], rsp_info->data[6], rsp_info->data[7],
|
||||
rsp_info->data[8], rsp_info->data[9]);
|
||||
|
||||
esp_bd_addr_t peer_addr;
|
||||
peer_addr[0] = rsp_info->data[7];
|
||||
peer_addr[1] = rsp_info->data[6];
|
||||
peer_addr[2] = rsp_info->data[5];
|
||||
peer_addr[3] = rsp_info->data[4];
|
||||
peer_addr[4] = rsp_info->data[3];
|
||||
peer_addr[5] = rsp_info->data[2];
|
||||
uint8_t adv_handle = param->pa_rsp_rpt_evt.adv_handle;
|
||||
uint8_t subevent = param->pa_rsp_rpt_evt.subevt;
|
||||
uint8_t phy_mask = ESP_BLE_PHY_1M_PREF_MASK;
|
||||
|
||||
// Connect using PAwR synced connection
|
||||
esp_ble_gatt_pawr_conn_params_t pawr_conn_params = {0};
|
||||
memcpy(pawr_conn_params.remote_bda, peer_addr, ESP_BD_ADDR_LEN);
|
||||
pawr_conn_params.remote_addr_type = BLE_ADDR_TYPE_PUBLIC;
|
||||
pawr_conn_params.own_addr_type = BLE_ADDR_TYPE_PUBLIC;
|
||||
pawr_conn_params.adv_handle = adv_handle;
|
||||
pawr_conn_params.subevent = subevent;
|
||||
pawr_conn_params.phy_mask = phy_mask;
|
||||
pawr_conn_params.phy_1m_conn_params = &conn_params;
|
||||
// Create PAwR synced connection
|
||||
esp_err_t ret = esp_ble_gattc_aux_open_with_pawr_synced(gl_profile_tab[PROFILE_A_APP_ID].gattc_if, &pawr_conn_params);
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to connect to device, error = 0x%x", ret);
|
||||
} else {
|
||||
ESP_LOGI(TAG, "Connection create sent, adv handle = %d, subevent = %d", adv_handle, subevent);
|
||||
ESP_LOG_BUFFER_HEX(TAG, peer_addr, ESP_BD_ADDR_LEN);
|
||||
is_connected = true;
|
||||
}
|
||||
} else if (is_connected) {
|
||||
ESP_LOGD(TAG, "Already connected, skipping connection attempt");
|
||||
} else if (gl_profile_tab[PROFILE_A_APP_ID].gattc_if == ESP_GATT_IF_NONE) {
|
||||
ESP_LOGW(TAG, "GATTC interface not ready, cannot connect");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ESP_LOGW(TAG, "Response slot:%d, data_status:%d",
|
||||
rsp_info->rsp_slot, rsp_info->data_status);
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief GATTC event handler
|
||||
*/
|
||||
static void gattc_profile_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param)
|
||||
{
|
||||
if (param == NULL) {
|
||||
ESP_LOGE(TAG, "GATTC event handler received NULL param");
|
||||
return;
|
||||
}
|
||||
|
||||
esp_ble_gattc_cb_param_t *p_data = (esp_ble_gattc_cb_param_t *)param;
|
||||
|
||||
switch (event) {
|
||||
case ESP_GATTC_REG_EVT: {
|
||||
ESP_LOGI(TAG, "GATTC register app_id %04x, status %d, gattc_if %d",
|
||||
p_data->reg.app_id, p_data->reg.status, gattc_if);
|
||||
if (p_data->reg.status == ESP_GATT_OK) {
|
||||
gl_profile_tab[PROFILE_A_APP_ID].gattc_if = gattc_if;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ESP_GATTC_CONNECT_EVT: {
|
||||
ESP_LOGI(TAG, "[Connection established], conn_id = 0x%04x", p_data->connect.conn_id);
|
||||
conn_id = p_data->connect.conn_id;
|
||||
memcpy(gl_profile_tab[PROFILE_A_APP_ID].remote_bda, p_data->connect.remote_bda, ESP_BD_ADDR_LEN);
|
||||
gl_profile_tab[PROFILE_A_APP_ID].conn_id = p_data->connect.conn_id;
|
||||
print_conn_info(conn_id, p_data->connect.remote_bda);
|
||||
|
||||
// Request MTU exchange
|
||||
esp_err_t mtu_ret = esp_ble_gattc_send_mtu_req(gattc_if, p_data->connect.conn_id);
|
||||
if (mtu_ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Config MTU error, error code = %x", mtu_ret);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ESP_GATTC_OPEN_EVT:
|
||||
if (p_data->open.status != ESP_GATT_OK) {
|
||||
ESP_LOGE(TAG, "GATTC open failed, status %d", p_data->open.status);
|
||||
break;
|
||||
}
|
||||
ESP_LOGI(TAG, "GATTC open successfully, conn_id = 0x%04x, MTU %u",
|
||||
p_data->open.conn_id, p_data->open.mtu);
|
||||
// Start service discovery
|
||||
esp_err_t ret = esp_ble_gattc_search_service(gattc_if, p_data->open.conn_id, NULL);
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Search service failed, error code = %x", ret);
|
||||
}
|
||||
break;
|
||||
case ESP_GATTC_CFG_MTU_EVT:
|
||||
ESP_LOGI(TAG, "MTU exchange, status %d, MTU %d", param->cfg_mtu.status, param->cfg_mtu.mtu);
|
||||
break;
|
||||
case ESP_GATTC_DIS_SRVC_CMPL_EVT:
|
||||
if (param->dis_srvc_cmpl.status != ESP_GATT_OK) {
|
||||
ESP_LOGE(TAG, "Service discover failed, status %d", param->dis_srvc_cmpl.status);
|
||||
break;
|
||||
}
|
||||
ESP_LOGI(TAG, "Service discover complete, conn_id %d", param->dis_srvc_cmpl.conn_id);
|
||||
// Search for specific service UUID
|
||||
esp_err_t search_ret = esp_ble_gattc_search_service(gattc_if, param->dis_srvc_cmpl.conn_id, &remote_filter_service_uuid);
|
||||
if (search_ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Search service failed, error code = %x", search_ret);
|
||||
}
|
||||
break;
|
||||
case ESP_GATTC_SEARCH_RES_EVT: {
|
||||
ESP_LOGI(TAG, "Service search result, conn_id = 0x%04x, is_primary %d",
|
||||
p_data->search_res.conn_id, p_data->search_res.is_primary);
|
||||
ESP_LOGI(TAG, "start handle %d, end handle %d",
|
||||
p_data->search_res.start_handle, p_data->search_res.end_handle);
|
||||
if (p_data->search_res.srvc_id.uuid.len == ESP_UUID_LEN_16 &&
|
||||
p_data->search_res.srvc_id.uuid.uuid.uuid16 == GATTS_SERVICE_UUID_PAWR_SYNC) {
|
||||
ESP_LOGI(TAG, "PAwR Sync Service found");
|
||||
get_server = true;
|
||||
gl_profile_tab[PROFILE_A_APP_ID].service_start_handle = p_data->search_res.start_handle;
|
||||
gl_profile_tab[PROFILE_A_APP_ID].service_end_handle = p_data->search_res.end_handle;
|
||||
ESP_LOGI(TAG, "Service UUID16: 0x%04x", p_data->search_res.srvc_id.uuid.uuid.uuid16);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ESP_GATTC_SEARCH_CMPL_EVT:
|
||||
if (p_data->search_cmpl.status != ESP_GATT_OK) {
|
||||
ESP_LOGE(TAG, "Service search failed, status 0x%02x", p_data->search_cmpl.status);
|
||||
break;
|
||||
}
|
||||
ESP_LOGI(TAG, "Service search complete");
|
||||
if (get_server) {
|
||||
uint16_t count = 0;
|
||||
esp_gatt_status_t status = esp_ble_gattc_get_attr_count(gattc_if,
|
||||
p_data->search_cmpl.conn_id,
|
||||
ESP_GATT_DB_CHARACTERISTIC,
|
||||
gl_profile_tab[PROFILE_A_APP_ID].service_start_handle,
|
||||
gl_profile_tab[PROFILE_A_APP_ID].service_end_handle,
|
||||
INVALID_HANDLE,
|
||||
&count);
|
||||
if (status != ESP_GATT_OK) {
|
||||
ESP_LOGE(TAG, "esp_ble_gattc_get_attr_count error, status = 0x%02x", status);
|
||||
break;
|
||||
}
|
||||
|
||||
if (count > 0) {
|
||||
esp_gattc_char_elem_t *char_elem_result = (esp_gattc_char_elem_t *)malloc(sizeof(esp_gattc_char_elem_t) * count);
|
||||
if (!char_elem_result) {
|
||||
ESP_LOGE(TAG, "gattc no mem");
|
||||
break;
|
||||
} else {
|
||||
status = esp_ble_gattc_get_char_by_uuid(gattc_if,
|
||||
p_data->search_cmpl.conn_id,
|
||||
gl_profile_tab[PROFILE_A_APP_ID].service_start_handle,
|
||||
gl_profile_tab[PROFILE_A_APP_ID].service_end_handle,
|
||||
remote_filter_char_uuid,
|
||||
char_elem_result,
|
||||
&count);
|
||||
if (status != ESP_GATT_OK) {
|
||||
ESP_LOGE(TAG, "esp_ble_gattc_get_char_by_uuid error, status = 0x%02x", status);
|
||||
free(char_elem_result);
|
||||
break;
|
||||
}
|
||||
|
||||
if (count > 0) {
|
||||
gl_profile_tab[PROFILE_A_APP_ID].char_handle = char_elem_result[0].char_handle;
|
||||
ESP_LOGI(TAG, "Characteristic found, handle = %d, properties = 0x%02x",
|
||||
char_elem_result[0].char_handle, char_elem_result[0].properties);
|
||||
|
||||
// Register for notify if supported
|
||||
if (char_elem_result[0].properties & ESP_GATT_CHAR_PROP_BIT_NOTIFY) {
|
||||
esp_err_t reg_ret = esp_ble_gattc_register_for_notify(gattc_if,
|
||||
gl_profile_tab[PROFILE_A_APP_ID].remote_bda,
|
||||
char_elem_result[0].char_handle);
|
||||
if (reg_ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Register for notify failed, error code = %x", reg_ret);
|
||||
}
|
||||
}
|
||||
}
|
||||
free(char_elem_result);
|
||||
}
|
||||
} else {
|
||||
ESP_LOGE(TAG, "no char found");
|
||||
}
|
||||
}
|
||||
break;
|
||||
case ESP_GATTC_REG_FOR_NOTIFY_EVT: {
|
||||
if (p_data->reg_for_notify.status != ESP_GATT_OK) {
|
||||
ESP_LOGE(TAG, "Notification register failed, status %d", p_data->reg_for_notify.status);
|
||||
break;
|
||||
}
|
||||
ESP_LOGI(TAG, "Notification register successfully, handle = %d", p_data->reg_for_notify.handle);
|
||||
|
||||
// Enable notify by writing to CCCD
|
||||
uint16_t count = 0;
|
||||
esp_gatt_status_t ret_status = esp_ble_gattc_get_attr_count(gattc_if,
|
||||
gl_profile_tab[PROFILE_A_APP_ID].conn_id,
|
||||
ESP_GATT_DB_DESCRIPTOR,
|
||||
gl_profile_tab[PROFILE_A_APP_ID].service_start_handle,
|
||||
gl_profile_tab[PROFILE_A_APP_ID].service_end_handle,
|
||||
gl_profile_tab[PROFILE_A_APP_ID].char_handle,
|
||||
&count);
|
||||
if (ret_status != ESP_GATT_OK) {
|
||||
ESP_LOGE(TAG, "esp_ble_gattc_get_attr_count error, status = 0x%02x", ret_status);
|
||||
break;
|
||||
}
|
||||
|
||||
if (count > 0) {
|
||||
esp_gattc_descr_elem_t *descr_elem_result = malloc(sizeof(esp_gattc_descr_elem_t) * count);
|
||||
if (!descr_elem_result) {
|
||||
ESP_LOGE(TAG, "malloc error, gattc no mem");
|
||||
break;
|
||||
}
|
||||
|
||||
ret_status = esp_ble_gattc_get_descr_by_char_handle(gattc_if,
|
||||
gl_profile_tab[PROFILE_A_APP_ID].conn_id,
|
||||
p_data->reg_for_notify.handle,
|
||||
notify_descr_uuid,
|
||||
descr_elem_result,
|
||||
&count);
|
||||
if (ret_status != ESP_GATT_OK) {
|
||||
ESP_LOGE(TAG, "esp_ble_gattc_get_descr_by_char_handle error, status = 0x%02x", ret_status);
|
||||
free(descr_elem_result);
|
||||
break;
|
||||
}
|
||||
|
||||
if (count > 0 && descr_elem_result[0].uuid.len == ESP_UUID_LEN_16 &&
|
||||
descr_elem_result[0].uuid.uuid.uuid16 == ESP_GATT_UUID_CHAR_CLIENT_CONFIG) {
|
||||
uint16_t notify_en = 1; // Enable notify
|
||||
ret_status = esp_ble_gattc_write_char_descr(gattc_if,
|
||||
gl_profile_tab[PROFILE_A_APP_ID].conn_id,
|
||||
descr_elem_result[0].handle,
|
||||
sizeof(notify_en),
|
||||
(uint8_t *)¬ify_en,
|
||||
ESP_GATT_WRITE_TYPE_RSP,
|
||||
ESP_GATT_AUTH_REQ_NONE);
|
||||
if (ret_status != ESP_GATT_OK) {
|
||||
ESP_LOGE(TAG, "esp_ble_gattc_write_char_descr error, status = 0x%02x", ret_status);
|
||||
} else {
|
||||
ESP_LOGI(TAG, "Enable notify sent");
|
||||
}
|
||||
}
|
||||
free(descr_elem_result);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ESP_GATTC_NOTIFY_EVT:
|
||||
if (p_data->notify.is_notify) {
|
||||
ESP_LOGI(TAG, "Notification received, handle = %d, len = %d",
|
||||
p_data->notify.handle, p_data->notify.value_len);
|
||||
} else {
|
||||
ESP_LOGI(TAG, "Indication received, handle = %d, len = %d",
|
||||
p_data->notify.handle, p_data->notify.value_len);
|
||||
}
|
||||
if (p_data->notify.value != NULL && p_data->notify.value_len > 0) {
|
||||
ESP_LOG_BUFFER_HEX(TAG, p_data->notify.value, p_data->notify.value_len);
|
||||
}
|
||||
break;
|
||||
case ESP_GATTC_WRITE_DESCR_EVT:
|
||||
if (p_data->write.status != ESP_GATT_OK) {
|
||||
ESP_LOGE(TAG, "Descriptor write failed, status 0x%02x", p_data->write.status);
|
||||
break;
|
||||
}
|
||||
ESP_LOGI(TAG, "Descriptor write successfully, notify enabled");
|
||||
break;
|
||||
case ESP_GATTC_READ_CHAR_EVT:
|
||||
if (p_data->read.status != ESP_GATT_OK) {
|
||||
ESP_LOGE(TAG, "Characteristic read failed, status 0x%02x", p_data->read.status);
|
||||
break;
|
||||
}
|
||||
ESP_LOGI(TAG, "Characteristic read successfully, handle = %d, len = %d",
|
||||
p_data->read.handle, p_data->read.value_len);
|
||||
if (p_data->read.value != NULL && p_data->read.value_len > 0) {
|
||||
ESP_LOG_BUFFER_HEX(TAG, p_data->read.value, p_data->read.value_len);
|
||||
}
|
||||
break;
|
||||
case ESP_GATTC_WRITE_CHAR_EVT:
|
||||
if (p_data->write.status != ESP_GATT_OK) {
|
||||
ESP_LOGE(TAG, "Characteristic write failed, status 0x%02x", p_data->write.status);
|
||||
break;
|
||||
}
|
||||
ESP_LOGI(TAG, "Characteristic write successfully");
|
||||
break;
|
||||
case ESP_GATTC_DISCONNECT_EVT:
|
||||
ESP_LOGI(TAG, "[Disconnected], conn_id = 0x%04x, reason = 0x%02x",
|
||||
p_data->disconnect.conn_id, p_data->disconnect.reason);
|
||||
is_connected = false;
|
||||
conn_id = 0xFFFF;
|
||||
get_server = false;
|
||||
gl_profile_tab[PROFILE_A_APP_ID].service_start_handle = INVALID_HANDLE;
|
||||
gl_profile_tab[PROFILE_A_APP_ID].service_end_handle = INVALID_HANDLE;
|
||||
gl_profile_tab[PROFILE_A_APP_ID].char_handle = INVALID_HANDLE;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Start periodic advertising with PAwR
|
||||
*/
|
||||
static void start_periodic_adv(void)
|
||||
{
|
||||
// Create static random address
|
||||
esp_bd_addr_t rand_addr;
|
||||
esp_ble_gap_addr_create_static(rand_addr);
|
||||
|
||||
ESP_LOG_BUFFER_HEX(TAG, rand_addr, ESP_BD_ADDR_LEN);
|
||||
|
||||
// Set extended advertising parameters
|
||||
ESP_LOGI(TAG, "Set extended advertising params...");
|
||||
FUNC_SEND_WAIT_SEM(esp_ble_gap_ext_adv_set_params(EXT_ADV_HANDLE, &ext_adv_params), test_sem);
|
||||
FUNC_SEND_WAIT_SEM(esp_ble_gap_ext_adv_set_rand_addr(EXT_ADV_HANDLE, rand_addr), test_sem);
|
||||
|
||||
// Set extended advertising data
|
||||
ESP_LOGI(TAG, "Set extended advertising data...");
|
||||
FUNC_SEND_WAIT_SEM(esp_ble_gap_config_ext_adv_data_raw(EXT_ADV_HANDLE,
|
||||
sizeof(raw_ext_adv_data),
|
||||
raw_ext_adv_data), test_sem);
|
||||
|
||||
// Start extended advertising
|
||||
ESP_LOGI(TAG, "Start extended advertising...");
|
||||
FUNC_SEND_WAIT_SEM(esp_ble_gap_ext_adv_start(NUM_EXT_ADV, ext_adv), test_sem);
|
||||
|
||||
// Set periodic advertising parameters
|
||||
ESP_LOGI(TAG, "Set periodic adv (PAwR) params...");
|
||||
FUNC_SEND_WAIT_SEM(esp_ble_gap_periodic_adv_set_params(EXT_ADV_HANDLE, &periodic_adv_params), test_sem);
|
||||
|
||||
// Start periodic advertising (PAwR)
|
||||
ESP_LOGI(TAG, "Start periodic advertising (PAwR)...");
|
||||
FUNC_SEND_WAIT_SEM(esp_ble_gap_periodic_adv_start(EXT_ADV_HANDLE), test_sem);
|
||||
|
||||
ESP_LOGI(TAG, "PAwR advertising started successfully");
|
||||
}
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
esp_err_t ret;
|
||||
|
||||
// Initialize NVS
|
||||
ret = nvs_flash_init();
|
||||
if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
|
||||
ESP_ERROR_CHECK(nvs_flash_erase());
|
||||
ret = nvs_flash_init();
|
||||
}
|
||||
ESP_ERROR_CHECK(ret);
|
||||
|
||||
ESP_ERROR_CHECK(esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT));
|
||||
|
||||
esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT();
|
||||
ret = esp_bt_controller_init(&bt_cfg);
|
||||
if (ret) {
|
||||
ESP_LOGE(TAG, "%s initialize controller failed: %s", __func__, esp_err_to_name(ret));
|
||||
return;
|
||||
}
|
||||
|
||||
ret = esp_bt_controller_enable(ESP_BT_MODE_BLE);
|
||||
if (ret) {
|
||||
ESP_LOGE(TAG, "%s enable controller failed: %s", __func__, esp_err_to_name(ret));
|
||||
return;
|
||||
}
|
||||
|
||||
esp_bluedroid_config_t cfg = BT_BLUEDROID_INIT_CONFIG_DEFAULT();
|
||||
ret = esp_bluedroid_init_with_cfg(&cfg);
|
||||
if (ret) {
|
||||
ESP_LOGE(TAG, "%s init bluetooth failed: %s", __func__, esp_err_to_name(ret));
|
||||
return;
|
||||
}
|
||||
|
||||
ret = esp_bluedroid_enable();
|
||||
if (ret) {
|
||||
ESP_LOGE(TAG, "%s enable bluetooth failed: %s", __func__, esp_err_to_name(ret));
|
||||
return;
|
||||
}
|
||||
|
||||
ret = esp_ble_gap_register_callback(gap_event_handler);
|
||||
if (ret) {
|
||||
ESP_LOGE(TAG, "gap register error, error code = %x", ret);
|
||||
return;
|
||||
}
|
||||
|
||||
ret = esp_ble_gattc_register_callback(gattc_profile_event_handler);
|
||||
if (ret) {
|
||||
ESP_LOGE(TAG, "gattc register error, error code = %x", ret);
|
||||
return;
|
||||
}
|
||||
|
||||
ret = esp_ble_gattc_app_register(PROFILE_A_APP_ID);
|
||||
if (ret) {
|
||||
ESP_LOGE(TAG, "gattc app register error, error code = %x", ret);
|
||||
return;
|
||||
}
|
||||
|
||||
// Create semaphore
|
||||
test_sem = xSemaphoreCreateBinary();
|
||||
|
||||
// Start periodic advertising with PAwR
|
||||
start_periodic_adv();
|
||||
|
||||
ESP_LOGI(TAG, "BLE PAwR Advertiser Connection example started");
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
# This file was generated using idf.py save-defconfig. It can be edited manually.
|
||||
# Espressif IoT Development Framework (ESP-IDF) Project Minimal Configuration
|
||||
#
|
||||
# BT config - Enable Bluetooth
|
||||
CONFIG_BT_ENABLED=y
|
||||
|
||||
# Enable Bluedroid host
|
||||
CONFIG_BT_BLUEDROID_ENABLED=y
|
||||
|
||||
# Enable BLE 5.0 features (required for PAwR)
|
||||
CONFIG_BT_BLE_50_FEATURES_SUPPORTED=y
|
||||
|
||||
# Enable BLE extend advertising (required for periodic advertising)
|
||||
CONFIG_BT_BLE_50_EXTEND_ADV_EN=y
|
||||
|
||||
# Enable BLE periodic advertising
|
||||
CONFIG_BT_BLE_50_PERIODIC_ADV_EN=y
|
||||
|
||||
# Enable periodic advertising enhancements (ADI support)
|
||||
CONFIG_BT_BLE_FEAT_PERIODIC_ADV_ENH=n
|
||||
|
||||
# Enable PAwR feature (Host)
|
||||
CONFIG_BT_BLE_FEAT_PAWR_EN=y
|
||||
|
||||
# Enable PAwR feature (Controller)
|
||||
CONFIG_BT_LE_PERIODIC_ADV_WITH_RESPONSE_ENABLED=y
|
||||
|
||||
CONFIG_BT_BLE_50_EXTEND_SCAN_EN=n
|
||||
|
||||
CONFIG_BT_BLE_50_DTM_TEST_EN=n
|
||||
|
||||
CONFIG_BT_BLE_SMP_ENABLE=n
|
||||
|
||||
CONFIG_BT_GATTS_ENABLE=n
|
||||
@@ -0,0 +1,8 @@
|
||||
# The following lines of boilerplate have to be in your project's CMakeLists
|
||||
# in this exact order for cmake to work correctly
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
# "Trim" the build. Include the minimal set of components, main, and anything it depends on.
|
||||
idf_build_set_property(MINIMAL_BUILD ON)
|
||||
project(ble_pawr_synchronizer_demos)
|
||||
@@ -0,0 +1,61 @@
|
||||
| Supported Targets | ESP32-C5 | ESP32-C6 | ESP32-C61 | ESP32-H2 |
|
||||
| ----------------- | -------- | -------- | --------- | -------- |
|
||||
|
||||
# ESP-IDF PAwR Synchronizer Example
|
||||
|
||||
This example demonstrates **Periodic Advertising with Responses (PAwR)** on the synchronizer side.
|
||||
|
||||
- The device performs **extended scanning** and creates **periodic sync** to the advertiser.
|
||||
- After sync is established, it selects a list of subevents to synchronize to via `esp_ble_gap_set_periodic_sync_subevent()`.
|
||||
- For received subevents, it sends responses via `esp_ble_gap_set_periodic_adv_response_data()`.
|
||||
- The response data includes the synchronizer's BLE address, allowing the advertiser to establish a connection.
|
||||
- The device acts as a **GATTS server** with a service table, ready to accept connections from the advertiser.
|
||||
|
||||
## How to Use
|
||||
|
||||
### Pairing Test: Synchronizer + Advertiser
|
||||
|
||||
This example should be paired with [ble_pawr_advertiser](../ble_pawr_advertiser) or [ble_pawr_advertiser_conn](../ble_pawr_advertiser_conn) for testing.
|
||||
|
||||
**Test Setup:**
|
||||
1. **Device A (Advertiser)**: Flash `ble_pawr_advertiser` or `ble_pawr_advertiser_conn` example
|
||||
2. **Device B (Synchronizer)**: Flash this `ble_pawr_synchronizer` example
|
||||
|
||||
**Steps:**
|
||||
1. Build and flash advertiser example to Device A:
|
||||
```bash
|
||||
cd examples/bluetooth/bluedroid/ble_50/ble_pawr_advertiser
|
||||
idf.py set-target <chip_name>
|
||||
idf.py -p <PORT_A> build flash monitor
|
||||
```
|
||||
|
||||
2. Build and flash synchronizer example to Device B:
|
||||
```bash
|
||||
cd examples/bluetooth/bluedroid/ble_50/ble_pawr_synchronizer
|
||||
idf.py set-target <chip_name>
|
||||
idf.py -p <PORT_B> build flash monitor
|
||||
```
|
||||
|
||||
3. **Power on Device A first** (advertiser starts broadcasting)
|
||||
4. **Power on Device B** (synchronizer starts scanning)
|
||||
5. Device B will automatically detect Device A by device name "ESP_EXTPAwR_ADV"
|
||||
6. Device B creates periodic sync and selects subevents
|
||||
7. Both devices exchange PAwR data
|
||||
|
||||
**Expected Log Hints:**
|
||||
|
||||
**On Synchronizer (Device B):**
|
||||
- `Extended scanning start, status 0`
|
||||
- `Local address cached: XX:XX:XX:XX:XX:XX, type = X`
|
||||
- `Create sync with the peer device ESP_EXTPAwR_ADV`
|
||||
- `Periodic advertising sync establish, status 0`
|
||||
- `PA sync subevent, status 0`
|
||||
- `create attribute table successfully, the number handle = X`
|
||||
- `Service started, service_handle = X`
|
||||
- `Set PA response data, status 0` (repeatedly when sending responses)
|
||||
- `[Connection established], conn_id = 0xXXXX` (when advertiser connects)
|
||||
|
||||
**On Advertiser (Device A):**
|
||||
- `PAwR advertising started successfully`
|
||||
- `[Request] data: 0x00, subevt start:0, subevt count:5`
|
||||
- `[Response] adv_handle:0, subevent:0, tx_status:0, num_rsp:1`
|
||||
@@ -0,0 +1,3 @@
|
||||
idf_component_register(SRCS "ble_pawr_synchronizer_demo.c"
|
||||
PRIV_REQUIRES bt nvs_flash
|
||||
INCLUDE_DIRS ".")
|
||||
@@ -0,0 +1,15 @@
|
||||
menu "Example 'PERIODIC SYNCHRONIZATION' Config"
|
||||
|
||||
config EXAMPLE_CI_ID
|
||||
int "example id for CI test"
|
||||
default 0
|
||||
help
|
||||
This config the example id for CI test. Only for internal used.
|
||||
|
||||
config EXAMPLE_CI_PIPELINE_ID
|
||||
int "The pipeline id for CI test"
|
||||
default 0
|
||||
help
|
||||
This config the pipeline id for CI test. Only for internal used.
|
||||
|
||||
endmenu
|
||||
@@ -0,0 +1,561 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Unlicense OR CC0-1.0
|
||||
*/
|
||||
|
||||
/****************************************************************************
|
||||
*
|
||||
* This demo showcases BLE Periodic Advertising with Responses (PAwR) on the synchronizer side.
|
||||
* The device performs extended scanning and creates periodic sync to the advertiser.
|
||||
* After sync is established, it selects a list of subevents to synchronize to via
|
||||
* esp_ble_gap_set_periodic_sync_subevent().
|
||||
* For received subevents, it sends responses via esp_ble_gap_set_periodic_adv_response_data().
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdint.h>
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "freertos/event_groups.h"
|
||||
#include "esp_system.h"
|
||||
#include "esp_log.h"
|
||||
#include "nvs_flash.h"
|
||||
#include "esp_bt.h"
|
||||
|
||||
#include "esp_gap_ble_api.h"
|
||||
#include "esp_gatts_api.h"
|
||||
#include "esp_bt_defs.h"
|
||||
#include "esp_bt_main.h"
|
||||
#include "esp_gatt_common_api.h"
|
||||
|
||||
#include "sdkconfig.h"
|
||||
|
||||
#include "freertos/semphr.h"
|
||||
|
||||
|
||||
#define FUNC_SEND_WAIT_SEM(func, sem) do {\
|
||||
esp_err_t __err_rc = (func);\
|
||||
if (__err_rc != ESP_OK) { \
|
||||
ESP_LOGE(LOG_TAG, "%s failed, error = %d", #func, __err_rc); \
|
||||
return; \
|
||||
} \
|
||||
xSemaphoreTake(sem, portMAX_DELAY); \
|
||||
} while(0);
|
||||
|
||||
#define LOG_TAG "PAwR_SYNC"
|
||||
#define EXT_SCAN_DURATION 0
|
||||
#define EXT_SCAN_PERIOD 0
|
||||
|
||||
#define PROFILE_A_APP_ID 0
|
||||
#define SVC_INST_ID 0
|
||||
|
||||
/* Service UUID and Characteristic UUID */
|
||||
#define GATTS_DEMO_CHAR_VAL_LEN_MAX 500
|
||||
|
||||
/* UUID values (must be variables, not macros, to take address) */
|
||||
static const uint16_t GATTS_SERVICE_UUID_PAWR_SYNC = 0x00FF;
|
||||
static const uint16_t GATTS_CHAR_UUID_PAWR_SYNC = 0xFF01;
|
||||
|
||||
/* Attributes State Machine */
|
||||
enum {
|
||||
IDX_SVC,
|
||||
IDX_CHAR_A,
|
||||
IDX_CHAR_VAL_A,
|
||||
IDX_CHAR_CFG_A,
|
||||
PAWR_SYNC_IDX_NB,
|
||||
};
|
||||
|
||||
static char remote_device_name[ESP_BLE_ADV_NAME_LEN_MAX] = "ESP_EXTPAwR_ADV";
|
||||
static SemaphoreHandle_t test_sem = NULL;
|
||||
uint8_t rsp_slot_idx = 0;
|
||||
|
||||
/* Cached local address (read once during initialization) */
|
||||
static esp_bd_addr_t cached_local_addr = {0};
|
||||
static uint8_t cached_local_addr_type = 0;
|
||||
static bool local_addr_cached = false;
|
||||
|
||||
// Forward declaration
|
||||
static void gatts_profile_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param);
|
||||
|
||||
// GATTS profile structure
|
||||
static struct gatts_profile_inst {
|
||||
esp_gatts_cb_t gatts_cb;
|
||||
uint16_t gatts_if;
|
||||
uint16_t app_id;
|
||||
uint16_t conn_id;
|
||||
esp_bd_addr_t remote_bda;
|
||||
} gl_profile_tab[1] = {
|
||||
[PROFILE_A_APP_ID] = {
|
||||
.gatts_cb = gatts_profile_event_handler, /* Set callback at initialization */
|
||||
.gatts_if = ESP_GATT_IF_NONE, /* Not get the gatt_if, so initial is ESP_GATT_IF_NONE */
|
||||
},
|
||||
};
|
||||
|
||||
/* Handle table for service attributes */
|
||||
static uint16_t pawr_sync_handle_table[PAWR_SYNC_IDX_NB];
|
||||
|
||||
/* UUID definitions */
|
||||
static const uint16_t primary_service_uuid = ESP_GATT_UUID_PRI_SERVICE;
|
||||
static const uint16_t character_declaration_uuid = ESP_GATT_UUID_CHAR_DECLARE;
|
||||
static const uint16_t character_client_config_uuid = ESP_GATT_UUID_CHAR_CLIENT_CONFIG;
|
||||
static const uint8_t char_prop_read_write_notify = ESP_GATT_CHAR_PROP_BIT_READ | ESP_GATT_CHAR_PROP_BIT_WRITE | ESP_GATT_CHAR_PROP_BIT_NOTIFY;
|
||||
static const uint8_t char_value[4] = {0x00, 0x00, 0x00, 0x00};
|
||||
static const uint8_t char_client_config_ccc[2] = {0x00, 0x00};
|
||||
|
||||
/* Full Database Description - Used to add attributes into the database */
|
||||
static const esp_gatts_attr_db_t gatt_db[PAWR_SYNC_IDX_NB] =
|
||||
{
|
||||
// Service Declaration
|
||||
[IDX_SVC] =
|
||||
{{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&primary_service_uuid, ESP_GATT_PERM_READ,
|
||||
sizeof(uint16_t), sizeof(GATTS_SERVICE_UUID_PAWR_SYNC), (uint8_t *)&GATTS_SERVICE_UUID_PAWR_SYNC}},
|
||||
|
||||
/* Characteristic Declaration */
|
||||
[IDX_CHAR_A] =
|
||||
{{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&character_declaration_uuid, ESP_GATT_PERM_READ,
|
||||
sizeof(uint8_t), sizeof(uint8_t), (uint8_t *)&char_prop_read_write_notify}},
|
||||
|
||||
/* Characteristic Value */
|
||||
[IDX_CHAR_VAL_A] =
|
||||
{{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&GATTS_CHAR_UUID_PAWR_SYNC, ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE,
|
||||
GATTS_DEMO_CHAR_VAL_LEN_MAX, sizeof(char_value), (uint8_t *)char_value}},
|
||||
|
||||
/* Client Characteristic Configuration Descriptor */
|
||||
[IDX_CHAR_CFG_A] =
|
||||
{{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&character_client_config_uuid, ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE,
|
||||
sizeof(uint16_t), sizeof(char_client_config_ccc), (uint8_t *)char_client_config_ccc}},
|
||||
};
|
||||
|
||||
static esp_ble_ext_scan_params_t ext_scan_params = {
|
||||
.own_addr_type = BLE_ADDR_TYPE_PUBLIC,
|
||||
.filter_policy = BLE_SCAN_FILTER_ALLOW_ALL,
|
||||
.scan_duplicate = BLE_SCAN_DUPLICATE_ENABLE,
|
||||
.cfg_mask = ESP_BLE_GAP_EXT_SCAN_CFG_UNCODE_MASK | ESP_BLE_GAP_EXT_SCAN_CFG_CODE_MASK,
|
||||
.uncoded_cfg = {BLE_SCAN_TYPE_ACTIVE, 40, 40},
|
||||
.coded_cfg = {BLE_SCAN_TYPE_ACTIVE, 40, 40},
|
||||
};
|
||||
|
||||
static esp_ble_gap_periodic_adv_sync_params_t periodic_adv_sync_params = {
|
||||
.filter_policy = 0,
|
||||
.sid = 0,
|
||||
.addr_type = BLE_ADDR_TYPE_RANDOM,
|
||||
.skip = 10,
|
||||
.sync_timeout = 1000,
|
||||
};
|
||||
|
||||
bool periodic_sync = false;
|
||||
|
||||
|
||||
static void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param)
|
||||
{
|
||||
switch (event) {
|
||||
case ESP_GAP_BLE_SET_EXT_SCAN_PARAMS_COMPLETE_EVT:
|
||||
xSemaphoreGive(test_sem);
|
||||
ESP_LOGI(LOG_TAG, "Extended scanning params set, status %d", param->set_ext_scan_params.status);
|
||||
break;
|
||||
case ESP_GAP_BLE_EXT_SCAN_START_COMPLETE_EVT:
|
||||
xSemaphoreGive(test_sem);
|
||||
ESP_LOGI(LOG_TAG, "Extended scanning start, status %d", param->ext_scan_start.status);
|
||||
|
||||
// Cache local address after scan starts successfully (address is ready at this point)
|
||||
if (param->ext_scan_start.status == ESP_BT_STATUS_SUCCESS && !local_addr_cached) {
|
||||
esp_err_t addr_ret = esp_ble_gap_get_local_used_addr(cached_local_addr, &cached_local_addr_type);
|
||||
if (addr_ret == ESP_OK) {
|
||||
local_addr_cached = true;
|
||||
ESP_LOGI(LOG_TAG, "Local address cached: "ESP_BD_ADDR_STR", type = %d",
|
||||
ESP_BD_ADDR_HEX(cached_local_addr), cached_local_addr_type);
|
||||
} else {
|
||||
ESP_LOGW(LOG_TAG, "Failed to get local address, error = 0x%x", addr_ret);
|
||||
}
|
||||
} else if (param->ext_scan_start.status != ESP_BT_STATUS_SUCCESS) {
|
||||
ESP_LOGE(LOG_TAG, "Extended scanning start failed, status = 0x%02x", param->ext_scan_start.status);
|
||||
}
|
||||
break;
|
||||
case ESP_GAP_BLE_EXT_SCAN_STOP_COMPLETE_EVT:
|
||||
xSemaphoreGive(test_sem);
|
||||
ESP_LOGI(LOG_TAG, "Extended scanning stop, status %d", param->ext_scan_stop.status);
|
||||
break;
|
||||
case ESP_GAP_BLE_PERIODIC_ADV_CREATE_SYNC_COMPLETE_EVT:
|
||||
ESP_LOGI(LOG_TAG, "Periodic advertising create sync, status %d", param->period_adv_create_sync.status);
|
||||
break;
|
||||
case ESP_GAP_BLE_PERIODIC_ADV_SYNC_CANCEL_COMPLETE_EVT:
|
||||
ESP_LOGI(LOG_TAG, "Periodic advertising sync cancel, status %d", param->period_adv_sync_cancel.status);
|
||||
break;
|
||||
case ESP_GAP_BLE_PERIODIC_ADV_SYNC_TERMINATE_COMPLETE_EVT:
|
||||
ESP_LOGI(LOG_TAG, "Periodic advertising sync terminate, status %d", param->period_adv_sync_term.status);
|
||||
break;
|
||||
case ESP_GAP_BLE_PERIODIC_ADV_SYNC_LOST_EVT:
|
||||
ESP_LOGI(LOG_TAG, "Periodic advertising sync lost, sync handle %d", param->periodic_adv_sync_lost.sync_handle);
|
||||
break;
|
||||
case ESP_GAP_BLE_PERIODIC_ADV_SYNC_ESTAB_EVT:
|
||||
ESP_LOGI(LOG_TAG, "Periodic advertising sync establish, status %d", param->periodic_adv_sync_estab.status);
|
||||
ESP_LOGI(LOG_TAG, "address "ESP_BD_ADDR_STR"", ESP_BD_ADDR_HEX(param->periodic_adv_sync_estab.adv_addr));
|
||||
ESP_LOGI(LOG_TAG, "sync handle %d sid %d perioic adv interval %d adv phy %d", param->periodic_adv_sync_estab.sync_handle,
|
||||
param->periodic_adv_sync_estab.sid,
|
||||
param->periodic_adv_sync_estab.period_adv_interval,
|
||||
param->periodic_adv_sync_estab.adv_phy);
|
||||
ESP_LOGI(LOG_TAG, "num_subevt %d subevt_interval %d rsp_slot_delay %d rsp_slot_spacing %d", param->periodic_adv_sync_estab.num_subevt,
|
||||
param->periodic_adv_sync_estab.subevt_interval,
|
||||
param->periodic_adv_sync_estab.rsp_slot_delay,
|
||||
param->periodic_adv_sync_estab.rsp_slot_spacing);
|
||||
|
||||
uint8_t subevents[5] = {0, 1, 2, 3, 4};
|
||||
esp_ble_per_sync_subevent_params sync_subevent_params = {0};
|
||||
sync_subevent_params.sync_handle = param->periodic_adv_sync_estab.sync_handle;
|
||||
sync_subevent_params.periodic_adv_properties = 0;
|
||||
sync_subevent_params.num_subevents_to_sync = 5;
|
||||
sync_subevent_params.subevent = malloc(sizeof(uint8_t) * 5);
|
||||
if (!sync_subevent_params.subevent) {
|
||||
ESP_LOGE(LOG_TAG, "No memory");
|
||||
break;
|
||||
}
|
||||
for (uint8_t i = 0; i < 5; i++)
|
||||
{
|
||||
sync_subevent_params.subevent[i] = subevents[i];
|
||||
}
|
||||
|
||||
esp_err_t ret = esp_ble_gap_set_periodic_sync_subevent(&sync_subevent_params);
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(LOG_TAG, "Failed to set periodic sync subevent, error = 0x%x", ret);
|
||||
}
|
||||
// Free memory after API call (data is deep copied by the API)
|
||||
free(sync_subevent_params.subevent);
|
||||
break;
|
||||
case ESP_GAP_BLE_EXT_ADV_REPORT_EVT: {
|
||||
uint8_t *adv_name = NULL;
|
||||
uint8_t adv_name_len = 0;
|
||||
adv_name = esp_ble_resolve_adv_data_by_type(param->ext_adv_report.params.adv_data,
|
||||
param->ext_adv_report.params.adv_data_len,
|
||||
ESP_BLE_AD_TYPE_NAME_CMPL,
|
||||
&adv_name_len);
|
||||
if ((adv_name != NULL) && (memcmp(adv_name, remote_device_name, adv_name_len) == 0) && !periodic_sync) {
|
||||
// Note: If there are multiple devices with the same device name, the device may sync to an unintended one.
|
||||
// It is recommended to change the default device name to ensure it is unique.
|
||||
periodic_sync = true;
|
||||
char adv_temp_name[30] = {'0'};
|
||||
size_t copy_len = (adv_name_len < sizeof(adv_temp_name)) ? adv_name_len : (sizeof(adv_temp_name) - 1);
|
||||
memcpy(adv_temp_name, adv_name, copy_len);
|
||||
adv_temp_name[copy_len] = '\0'; // Ensure null termination
|
||||
ESP_LOGI(LOG_TAG, "Create sync with the peer device %s", adv_temp_name);
|
||||
periodic_adv_sync_params.sid = param->ext_adv_report.params.sid;
|
||||
periodic_adv_sync_params.addr_type = param->ext_adv_report.params.addr_type;
|
||||
memcpy(periodic_adv_sync_params.addr, param->ext_adv_report.params.addr, sizeof(esp_bd_addr_t));
|
||||
esp_ble_gap_periodic_adv_create_sync(&periodic_adv_sync_params);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case ESP_GAP_BLE_PERIODIC_ADV_REPORT_EVT:
|
||||
// ESP_LOGI(LOG_TAG, "Periodic adv report, sync handle %d, data status %d, data len %d, rssi %d", param->period_adv_report.params.sync_handle,
|
||||
// param->period_adv_report.params.data_status,
|
||||
// param->period_adv_report.params.data_length,
|
||||
// param->period_adv_report.params.rssi);
|
||||
//ESP_LOGI(LOG_TAG, "periodic_evt_counter %d subevt %d", param->period_adv_report.params.periodic_evt_counter, param->period_adv_report.params.subevt);
|
||||
if (param->period_adv_report.params.subevt == 0xFF) {
|
||||
break;
|
||||
}
|
||||
rsp_slot_idx = (uint8_t)((rsp_slot_idx + 1) % 5);
|
||||
esp_ble_per_adv_response_data_params rsp_data_params = {0};
|
||||
|
||||
rsp_data_params.sync_handle = param->period_adv_report.params.sync_handle;
|
||||
rsp_data_params.request_event = param->period_adv_report.params.periodic_evt_counter;
|
||||
rsp_data_params.request_subevent = param->period_adv_report.params.subevt;
|
||||
rsp_data_params.response_subevent = param->period_adv_report.params.subevt;
|
||||
rsp_data_params.response_slot = rsp_slot_idx; // 0..4
|
||||
|
||||
// Use cached local address (read once after scan starts)
|
||||
if (!local_addr_cached) {
|
||||
ESP_LOGW(LOG_TAG, "Local address not cached yet, skipping address in response");
|
||||
// Fall back to original behavior without address
|
||||
// Note: data_length is uint8_t (max 255), so no overflow check needed
|
||||
rsp_data_params.response_data_len = param->period_adv_report.params.data_length;
|
||||
if (rsp_data_params.response_data_len > 0) {
|
||||
rsp_data_params.response_data = malloc(rsp_data_params.response_data_len);
|
||||
if (rsp_data_params.response_data) {
|
||||
memcpy(rsp_data_params.response_data, param->period_adv_report.params.data, rsp_data_params.response_data_len);
|
||||
esp_err_t ret = esp_ble_gap_set_periodic_adv_response_data(&rsp_data_params);
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(LOG_TAG, "Failed to set periodic adv response data, error = 0x%x", ret);
|
||||
}
|
||||
free(rsp_data_params.response_data);
|
||||
} else {
|
||||
ESP_LOGE(LOG_TAG, "Failed to allocate memory for response data");
|
||||
}
|
||||
} else {
|
||||
rsp_data_params.response_data = NULL;
|
||||
esp_err_t ret = esp_ble_gap_set_periodic_adv_response_data(&rsp_data_params);
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(LOG_TAG, "Failed to set periodic adv response data, error = 0x%x", ret);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// Format: [0x00, 0x02, addr[5], addr[4], addr[3], addr[2], addr[1], addr[0], ...original_data]
|
||||
// Total length: 8 bytes (address header) + original data length
|
||||
const uint8_t ADDR_HEADER_LEN = 8;
|
||||
// Note: data_length is uint8_t (max 255), and response_data_len is also uint8_t (max 255)
|
||||
// Check if adding ADDR_HEADER_LEN would exceed uint8_t range
|
||||
if (param->period_adv_report.params.data_length > (UINT8_MAX - ADDR_HEADER_LEN)) {
|
||||
ESP_LOGE(LOG_TAG, "Data length too large, would exceed uint8_t range: %d", param->period_adv_report.params.data_length);
|
||||
break;
|
||||
}
|
||||
rsp_data_params.response_data_len = ADDR_HEADER_LEN + param->period_adv_report.params.data_length;
|
||||
rsp_data_params.response_data = malloc(rsp_data_params.response_data_len);
|
||||
if (rsp_data_params.response_data) {
|
||||
// Set address header: [0x00, 0x02, addr[5], addr[4], addr[3], addr[2], addr[1], addr[0]]
|
||||
rsp_data_params.response_data[0] = 0x00;
|
||||
rsp_data_params.response_data[1] = 0x02;
|
||||
rsp_data_params.response_data[2] = cached_local_addr[5]; // MSB
|
||||
rsp_data_params.response_data[3] = cached_local_addr[4];
|
||||
rsp_data_params.response_data[4] = cached_local_addr[3];
|
||||
rsp_data_params.response_data[5] = cached_local_addr[2];
|
||||
rsp_data_params.response_data[6] = cached_local_addr[1];
|
||||
rsp_data_params.response_data[7] = cached_local_addr[0]; // LSB
|
||||
|
||||
// Append original received data
|
||||
if (param->period_adv_report.params.data_length > 0) {
|
||||
memcpy(&rsp_data_params.response_data[ADDR_HEADER_LEN],
|
||||
param->period_adv_report.params.data,
|
||||
param->period_adv_report.params.data_length);
|
||||
}
|
||||
|
||||
esp_err_t ret = esp_ble_gap_set_periodic_adv_response_data(&rsp_data_params);
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(LOG_TAG, "Failed to set periodic adv response data, error = 0x%x", ret);
|
||||
}
|
||||
// Free memory after API call (data is deep copied by the API)
|
||||
free(rsp_data_params.response_data);
|
||||
} else {
|
||||
ESP_LOGE(LOG_TAG, "Failed to allocate memory for response data");
|
||||
}
|
||||
|
||||
break;
|
||||
case ESP_GAP_BLE_SET_PERIODIC_ADV_SUBEVT_DATA_EVT:
|
||||
ESP_LOGI(LOG_TAG, "Set PA subevent data, status %d", param->pa_subevt_data_evt.status);
|
||||
break;
|
||||
case ESP_GAP_BLE_SET_PERIODIC_ADV_RESPONSE_DATA_EVT:
|
||||
if (param->pa_rsp_data_evt.status != ESP_BT_STATUS_SUCCESS)
|
||||
{
|
||||
ESP_LOGE(LOG_TAG, "Failed to set periodic adv response data, error = 0x%x", param->pa_rsp_data_evt.status);
|
||||
}
|
||||
// ESP_LOGI(LOG_TAG, "Set PA response data, status %d", param->pa_rsp_data_evt.status);
|
||||
break;
|
||||
case ESP_GAP_BLE_SET_PERIODIC_SYNC_SUBEVT_EVT:
|
||||
ESP_LOGI(LOG_TAG, "PA sync subevent, status %d", param->pa_sync_subevt_evt.status);
|
||||
break;
|
||||
case ESP_GAP_BLE_PERIODIC_ADV_SUBEVT_DATA_REQUEST_EVT:
|
||||
ESP_LOGI(LOG_TAG, "PA subevent data req, hdl %d start %d count %d", param->pa_subevt_data_req_evt.adv_handle, param->pa_subevt_data_req_evt.subevt_start, param->pa_subevt_data_req_evt.subevt_data_count);
|
||||
break;
|
||||
case ESP_GAP_BLE_PERIODIC_ADV_RESPONSE_REPORT_EVT:
|
||||
ESP_LOGI(LOG_TAG, "Set Periodic advertising response report, adv_handle %d", param->pa_rsp_rpt_evt.adv_handle);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief GATTS event handler
|
||||
*/
|
||||
static void gatts_profile_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param)
|
||||
{
|
||||
if (param == NULL) {
|
||||
ESP_LOGE(LOG_TAG, "GATTS event handler received NULL param");
|
||||
return;
|
||||
}
|
||||
|
||||
switch (event) {
|
||||
case ESP_GATTS_REG_EVT: {
|
||||
ESP_LOGI(LOG_TAG, "GATTS register app_id %04x, status %d, gatts_if %d",
|
||||
param->reg.app_id, param->reg.status, gatts_if);
|
||||
if (param->reg.status == ESP_GATT_OK) {
|
||||
gl_profile_tab[PROFILE_A_APP_ID].gatts_if = gatts_if;
|
||||
|
||||
// Create attribute table
|
||||
esp_err_t create_attr_ret = esp_ble_gatts_create_attr_tab(gatt_db, gatts_if, PAWR_SYNC_IDX_NB, SVC_INST_ID);
|
||||
if (create_attr_ret != ESP_OK) {
|
||||
ESP_LOGE(LOG_TAG, "create attr table failed, error code = %x", create_attr_ret);
|
||||
}
|
||||
} else {
|
||||
ESP_LOGE(LOG_TAG, "GATTS register failed, app_id %04x, status %d",
|
||||
param->reg.app_id, param->reg.status);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ESP_GATTS_CREAT_ATTR_TAB_EVT: {
|
||||
if (param->add_attr_tab.status != ESP_GATT_OK) {
|
||||
ESP_LOGE(LOG_TAG, "create attribute table failed, error code=0x%x", param->add_attr_tab.status);
|
||||
} else if (param->add_attr_tab.num_handle != PAWR_SYNC_IDX_NB) {
|
||||
ESP_LOGE(LOG_TAG, "create attribute table abnormally, num_handle (%d) doesn't equal to PAWR_SYNC_IDX_NB(%d)",
|
||||
param->add_attr_tab.num_handle, PAWR_SYNC_IDX_NB);
|
||||
} else {
|
||||
ESP_LOGI(LOG_TAG, "create attribute table successfully, the number handle = %d", param->add_attr_tab.num_handle);
|
||||
memcpy(pawr_sync_handle_table, param->add_attr_tab.handles, sizeof(pawr_sync_handle_table));
|
||||
esp_ble_gatts_start_service(pawr_sync_handle_table[IDX_SVC]);
|
||||
ESP_LOGI(LOG_TAG, "Service started, service_handle = %d", pawr_sync_handle_table[IDX_SVC]);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ESP_GATTS_READ_EVT:
|
||||
ESP_LOGI(LOG_TAG, "ESP_GATTS_READ_EVT, handle = %d", param->read.handle);
|
||||
break;
|
||||
case ESP_GATTS_WRITE_EVT:
|
||||
ESP_LOGI(LOG_TAG, "ESP_GATTS_WRITE_EVT, handle = %d, len = %d", param->write.handle, param->write.len);
|
||||
if (param->write.value != NULL && param->write.len > 0) {
|
||||
ESP_LOG_BUFFER_HEX(LOG_TAG, param->write.value, param->write.len);
|
||||
}
|
||||
|
||||
// Handle CCCD write (notify enable/disable)
|
||||
// Check if handle table is initialized (service created successfully)
|
||||
if (pawr_sync_handle_table[IDX_CHAR_CFG_A] != 0 &&
|
||||
pawr_sync_handle_table[IDX_CHAR_CFG_A] == param->write.handle &&
|
||||
param->write.len == 2 && param->write.value != NULL) {
|
||||
uint16_t descr_value = param->write.value[1] << 8 | param->write.value[0];
|
||||
if (descr_value == 0x0001) {
|
||||
ESP_LOGI(LOG_TAG, "notify enable");
|
||||
} else if (descr_value == 0x0000) {
|
||||
ESP_LOGI(LOG_TAG, "notify disable");
|
||||
}
|
||||
}
|
||||
|
||||
if (param->write.need_rsp) {
|
||||
esp_ble_gatts_send_response(gatts_if, param->write.conn_id, param->write.trans_id, ESP_GATT_OK, NULL);
|
||||
}
|
||||
break;
|
||||
case ESP_GATTS_START_EVT:
|
||||
ESP_LOGI(LOG_TAG, "SERVICE_START_EVT, status %d, service_handle %d", param->start.status, param->start.service_handle);
|
||||
break;
|
||||
case ESP_GATTS_CONNECT_EVT: {
|
||||
ESP_LOGI(LOG_TAG, "[Connection established], conn_id = 0x%04x", param->connect.conn_id);
|
||||
gl_profile_tab[PROFILE_A_APP_ID].conn_id = param->connect.conn_id;
|
||||
memcpy(gl_profile_tab[PROFILE_A_APP_ID].remote_bda, param->connect.remote_bda, ESP_BD_ADDR_LEN);
|
||||
ESP_LOGI(LOG_TAG, "Remote Address: "ESP_BD_ADDR_STR"", ESP_BD_ADDR_HEX(param->connect.remote_bda));
|
||||
break;
|
||||
}
|
||||
case ESP_GATTS_DISCONNECT_EVT:
|
||||
ESP_LOGI(LOG_TAG, "[Disconnected], conn_id = 0x%04x, reason = 0x%02x",
|
||||
param->disconnect.conn_id, param->disconnect.reason);
|
||||
gl_profile_tab[PROFILE_A_APP_ID].conn_id = 0xFFFF;
|
||||
break;
|
||||
case ESP_GATTS_MTU_EVT:
|
||||
ESP_LOGI(LOG_TAG, "MTU update event, conn_id = 0x%04x, mtu = %d",
|
||||
param->mtu.conn_id, param->mtu.mtu);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief GATTS callback function
|
||||
*/
|
||||
static void esp_gatts_cb(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param)
|
||||
{
|
||||
if (param == NULL) {
|
||||
ESP_LOGE(LOG_TAG, "GATTS callback received NULL param");
|
||||
return;
|
||||
}
|
||||
|
||||
/* If event is register event, store the gatts_if for each profile */
|
||||
if (event == ESP_GATTS_REG_EVT) {
|
||||
if (param->reg.status == ESP_GATT_OK) {
|
||||
gl_profile_tab[param->reg.app_id].gatts_if = gatts_if;
|
||||
} else {
|
||||
ESP_LOGE(LOG_TAG, "reg app failed, app_id %04x, status %d",
|
||||
param->reg.app_id, param->reg.status);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/* If the gatts_if equal to profile A, call profile A cb handler */
|
||||
do {
|
||||
int idx;
|
||||
for (idx = 0; idx < 1; idx++) {
|
||||
if (gatts_if == ESP_GATT_IF_NONE || /* ESP_GATT_IF_NONE, not specify a certain gatt_if, need to call every profile cb function */
|
||||
gatts_if == gl_profile_tab[idx].gatts_if) {
|
||||
if (gl_profile_tab[idx].gatts_cb) {
|
||||
gl_profile_tab[idx].gatts_cb(event, gatts_if, param);
|
||||
}
|
||||
}
|
||||
}
|
||||
} while (0);
|
||||
}
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
esp_err_t ret;
|
||||
|
||||
// Initialize NVS.
|
||||
ret = nvs_flash_init();
|
||||
if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
|
||||
ESP_ERROR_CHECK(nvs_flash_erase());
|
||||
ret = nvs_flash_init();
|
||||
}
|
||||
ESP_ERROR_CHECK( ret );
|
||||
|
||||
#if CONFIG_EXAMPLE_CI_PIPELINE_ID
|
||||
memcpy(remote_device_name, esp_bluedroid_get_example_name(), sizeof(remote_device_name));
|
||||
#endif
|
||||
|
||||
ESP_ERROR_CHECK(esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT));
|
||||
|
||||
esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT();
|
||||
ret = esp_bt_controller_init(&bt_cfg);
|
||||
if (ret) {
|
||||
ESP_LOGE(LOG_TAG, "%s initialize controller failed: %s", __func__, esp_err_to_name(ret));
|
||||
return;
|
||||
}
|
||||
|
||||
ret = esp_bt_controller_enable(ESP_BT_MODE_BLE);
|
||||
if (ret) {
|
||||
ESP_LOGE(LOG_TAG, "%s enable controller failed: %s", __func__, esp_err_to_name(ret));
|
||||
return;
|
||||
}
|
||||
|
||||
esp_bluedroid_config_t cfg = BT_BLUEDROID_INIT_CONFIG_DEFAULT();
|
||||
ret = esp_bluedroid_init_with_cfg(&cfg);
|
||||
if (ret) {
|
||||
ESP_LOGE(LOG_TAG, "%s init bluetooth failed: %s", __func__, esp_err_to_name(ret));
|
||||
return;
|
||||
}
|
||||
ret = esp_bluedroid_enable();
|
||||
if (ret) {
|
||||
ESP_LOGE(LOG_TAG, "%s enable bluetooth failed: %s", __func__, esp_err_to_name(ret));
|
||||
return;
|
||||
}
|
||||
|
||||
ret = esp_ble_gap_register_callback(gap_event_handler);
|
||||
if (ret){
|
||||
ESP_LOGE(LOG_TAG, "gap register error, error code = %x", ret);
|
||||
return;
|
||||
}
|
||||
|
||||
// Register GATTS callback
|
||||
ret = esp_ble_gatts_register_callback(esp_gatts_cb);
|
||||
if (ret){
|
||||
ESP_LOGE(LOG_TAG, "gatts register error, error code = %x", ret);
|
||||
return;
|
||||
}
|
||||
|
||||
// Register GATTS app (this will trigger ESP_GATTS_REG_EVT event immediately)
|
||||
ret = esp_ble_gatts_app_register(PROFILE_A_APP_ID);
|
||||
if (ret){
|
||||
ESP_LOGE(LOG_TAG, "gatts app register error, error code = %x", ret);
|
||||
return;
|
||||
}
|
||||
|
||||
vTaskDelay(200 / portTICK_PERIOD_MS);
|
||||
|
||||
test_sem = xSemaphoreCreateBinary();
|
||||
|
||||
FUNC_SEND_WAIT_SEM(esp_ble_gap_set_ext_scan_params(&ext_scan_params), test_sem);
|
||||
FUNC_SEND_WAIT_SEM(esp_ble_gap_start_ext_scan(EXT_SCAN_DURATION, EXT_SCAN_PERIOD), test_sem);
|
||||
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
CONFIG_EXAMPLE_CI_ID=8
|
||||
CONFIG_IDF_TARGET="esp32c2"
|
||||
CONFIG_XTAL_FREQ_26=y
|
||||
CONFIG_EXAMPLE_CI_PIPELINE_ID=${CI_PIPELINE_ID}
|
||||
@@ -0,0 +1,2 @@
|
||||
CONFIG_EXAMPLE_CI_ID=8
|
||||
CONFIG_EXAMPLE_CI_PIPELINE_ID=${CI_PIPELINE_ID}
|
||||
@@ -0,0 +1,14 @@
|
||||
CONFIG_BT_ENABLED=y
|
||||
CONFIG_BT_BLUEDROID_ENABLED=y
|
||||
CONFIG_BT_BLE_50_FEATURES_SUPPORTED=y
|
||||
CONFIG_BT_BLE_42_FEATURES_SUPPORTED=n
|
||||
CONFIG_BT_BLE_50_EXTEND_SCAN_EN=y
|
||||
CONFIG_BT_BLE_50_EXTEND_SYNC_EN=y
|
||||
CONFIG_BT_BLE_FEAT_PAWR_EN=y
|
||||
CONFIG_BT_LE_PERIODIC_ADV_WITH_RESPONSE_ENABLED=y
|
||||
|
||||
CONFIG_BT_GATTC_ENABLE=n
|
||||
|
||||
CONFIG_BT_BLE_SMP_ENABLE=n
|
||||
|
||||
CONFIG_BT_BLE_50_EXTEND_ADV_EN=n
|
||||
@@ -0,0 +1,7 @@
|
||||
# This file was generated using idf.py save-defconfig. It can be edited manually.
|
||||
# Espressif IoT Development Framework (ESP-IDF) Project Minimal Configuration
|
||||
#
|
||||
CONFIG_IDF_TARGET="esp32c2"
|
||||
CONFIG_BT_ENABLED=y
|
||||
CONFIG_FREERTOS_IDLE_TASK_STACKSIZE=2304
|
||||
# CONFIG_NEWLIB_NANO_FORMAT is not set
|
||||
@@ -0,0 +1,5 @@
|
||||
# This file was generated using idf.py save-defconfig. It can be edited manually.
|
||||
# Espressif IoT Development Framework (ESP-IDF) Project Minimal Configuration
|
||||
#
|
||||
CONFIG_IDF_TARGET="esp32c3"
|
||||
CONFIG_BT_ENABLED=y
|
||||
@@ -0,0 +1,5 @@
|
||||
# This file was generated using idf.py save-defconfig. It can be edited manually.
|
||||
# Espressif IoT Development Framework (ESP-IDF) Project Minimal Configuration
|
||||
#
|
||||
CONFIG_IDF_TARGET="esp32s3"
|
||||
CONFIG_BT_ENABLED=y
|
||||
@@ -0,0 +1,8 @@
|
||||
# The following lines of boilerplate have to be in your project's CMakeLists
|
||||
# in this exact order for cmake to work correctly
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
# "Trim" the build. Include the minimal set of components, main, and anything it depends on.
|
||||
idf_build_set_property(MINIMAL_BUILD ON)
|
||||
project(periodic_adv_with_cte_demos)
|
||||
@@ -0,0 +1,79 @@
|
||||
| Supported Targets | ESP32-C5 | ESP32-C61 | ESP32-H2 |
|
||||
| ----------------- | -------- | --------- | -------- |
|
||||
|
||||
# ESP-IDF BLE Periodic Advertising with CTE Example
|
||||
|
||||
This example demonstrates how to use BLE Periodic Advertising with Constant Tone Extension (CTE) feature using Bluedroid host API.
|
||||
|
||||
## What is BLE Periodic Advertising with CTE?
|
||||
|
||||
BLE Periodic Advertising with CTE combines periodic advertising with Constant Tone Extension to enable Direction Finding (AoA/AoD) functionality in periodic advertising scenarios. This allows devices to determine the direction of a Bluetooth signal during periodic advertising.
|
||||
|
||||
## How It Works
|
||||
|
||||
1. The device starts extended advertising
|
||||
2. The device enables periodic advertising
|
||||
3. The device configures CTE parameters for periodic advertising
|
||||
4. The device transmits periodic advertising packets with CTE
|
||||
5. IQ samples can be received for direction finding
|
||||
|
||||
## API Usage
|
||||
|
||||
- `esp_ble_gap_ext_adv_start()` - Start extended advertising
|
||||
- `esp_ble_gap_periodic_adv_start()` - Start periodic advertising
|
||||
- `esp_ble_cte_set_periodic_adv_transmit_params()` - Set CTE transmit parameters for periodic advertising
|
||||
- `esp_ble_cte_periodic_adv_response_enable()` - Enable CTE response for periodic advertising
|
||||
|
||||
## Configuration
|
||||
|
||||
The example requires BLE 5.0 CTE and periodic advertising features to be enabled:
|
||||
- `CONFIG_BT_BLE_50_FEATURES_SUPPORTED=y`
|
||||
- `CONFIG_BT_BLE_50_EXTEND_ADV_EN=y`
|
||||
- `CONFIG_BT_BLE_50_PERIODIC_ADV_EN=y`
|
||||
- `CONFIG_BT_BLE_FEAT_CTE_EN=y`
|
||||
- `CONFIG_BT_LE_CTE_FEATURE_ENABLED=y`
|
||||
|
||||
## Hardware Required
|
||||
|
||||
* A development board with ESP32-C5, ESP32-C61, or ESP32-H2 SoC that supports BLE 5.0 CTE
|
||||
* A USB cable for Power supply and programming
|
||||
|
||||
See [Development Boards](https://www.espressif.com/en/products/devkits) for more information about it.
|
||||
|
||||
## How to Use Example
|
||||
|
||||
Before project configuration and build, be sure to set the correct chip target using:
|
||||
|
||||
```bash
|
||||
idf.py set-target esp32c5 # or esp32c61 or esp32h2
|
||||
```
|
||||
|
||||
To test this demo, you can run the [ble_periodic_sync_with_cte](../ble_periodic_sync_with_cte) example on another device, which will synchronize with the periodic advertising and receive CTE.
|
||||
|
||||
### 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
|
||||
|
||||
The example will output periodic advertising and CTE-related events including:
|
||||
- Extended advertising start completion
|
||||
- Periodic advertising start completion
|
||||
- CTE parameter configuration completion
|
||||
- CTE response enable completion
|
||||
|
||||
```
|
||||
I (xxx) CTE_TEST: Extended advertising started
|
||||
I (xxx) CTE_TEST: Periodic advertising started
|
||||
I (xxx) CTE_TEST: CTE set periodic adv transmit params, status 0
|
||||
I (xxx) CTE_TEST: CTE periodic adv response enable, status 0
|
||||
...
|
||||
```
|
||||
|
||||
## 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 "periodic_adv_with_cte_demo.c"
|
||||
PRIV_REQUIRES bt nvs_flash
|
||||
INCLUDE_DIRS ".")
|
||||
@@ -0,0 +1,266 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Unlicense OR CC0-1.0
|
||||
*/
|
||||
|
||||
/****************************************************************************
|
||||
*
|
||||
* This demo showcases BLE GATT server. It can send adv data, be connected by client.
|
||||
* Run the gatt_client demo, the client demo will automatically connect to the gatt_server demo.
|
||||
* Client demo will enable gatt_server's notify after connection. The two devices will then exchange
|
||||
* data.
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "freertos/event_groups.h"
|
||||
#include "esp_system.h"
|
||||
#include "esp_log.h"
|
||||
#include "nvs_flash.h"
|
||||
#include "esp_bt.h"
|
||||
|
||||
#include "esp_gap_ble_api.h"
|
||||
#include "esp_gatts_api.h"
|
||||
#include "esp_bt_defs.h"
|
||||
#include "esp_bt_main.h"
|
||||
#include "esp_gatt_common_api.h"
|
||||
|
||||
#include "sdkconfig.h"
|
||||
|
||||
#include "freertos/semphr.h"
|
||||
#include "esp_ble_cte_api.h"
|
||||
|
||||
#define LOG_TAG "CTE_TEST"
|
||||
|
||||
#define FUNC_SEND_WAIT_SEM(func, sem) do {\
|
||||
esp_err_t __err_rc = (func);\
|
||||
if (__err_rc != ESP_OK) { \
|
||||
ESP_LOGE(LOG_TAG, "%s, message send fail, error = %d", __func__, __err_rc); \
|
||||
} \
|
||||
xSemaphoreTake(sem, portMAX_DELAY); \
|
||||
} while(0);
|
||||
|
||||
#define EXT_ADV_HANDLE 0
|
||||
#define NUM_EXT_ADV 1
|
||||
|
||||
static SemaphoreHandle_t test_sem = NULL;
|
||||
|
||||
|
||||
uint8_t addr_2m[6] = {0xc0, 0xde, 0x52, 0x00, 0x00, 0x02};
|
||||
|
||||
esp_ble_gap_ext_adv_params_t ext_adv_params_2M = {
|
||||
.type = ESP_BLE_GAP_SET_EXT_ADV_PROP_NONCONN_NONSCANNABLE_UNDIRECTED,
|
||||
.interval_min = 0x30,
|
||||
.interval_max = 0x30,
|
||||
.channel_map = ADV_CHNL_ALL,
|
||||
.filter_policy = ADV_FILTER_ALLOW_SCAN_ANY_CON_ANY,
|
||||
.primary_phy = ESP_BLE_GAP_PHY_1M,
|
||||
.max_skip = 0,
|
||||
.secondary_phy = ESP_BLE_GAP_PHY_2M,
|
||||
.sid = 0,
|
||||
.scan_req_notif = false,
|
||||
.own_addr_type = BLE_ADDR_TYPE_RANDOM,
|
||||
.tx_power = EXT_ADV_TX_PWR_NO_PREFERENCE,
|
||||
};
|
||||
|
||||
static esp_ble_gap_periodic_adv_params_t periodic_adv_params = {
|
||||
.interval_min = 0x40, // 80 ms interval
|
||||
.interval_max = 0x40,
|
||||
.properties = 0, // Do not include TX power
|
||||
};
|
||||
|
||||
static uint8_t periodic_adv_raw_data[] = {
|
||||
0x02, 0x01, 0x06,
|
||||
0x02, 0x0a, 0xeb,
|
||||
0x03, 0x03, 0xab, 0xcd,
|
||||
0x12, 0x09, 'E', 'S', 'P', '_', 'C', 'T', 'E', '_', 'B', 'L', 'U',
|
||||
'E', 'D', 'R', 'O', 'I', 'D'
|
||||
};
|
||||
|
||||
static uint8_t raw_ext_adv_data_2m[] = {
|
||||
0x02, 0x01, 0x06,
|
||||
0x02, 0x0a, 0xeb,
|
||||
0x12, 0x09, 'E', 'S', 'P', '_', 'C', 'T', 'E', '_', 'B', 'L', 'U',
|
||||
'E', 'D', 'R', 'O', 'I', 'D'
|
||||
};
|
||||
|
||||
static esp_ble_gap_ext_adv_t ext_adv[1] = {
|
||||
// instance, duration, period
|
||||
[0] = {EXT_ADV_HANDLE, 0, 0},
|
||||
};
|
||||
|
||||
static uint8_t antenna_ids[5] = {0x00, 0x01, 0x02, 0x03, 0x04};
|
||||
|
||||
static esp_ble_cte_connless_trans_params_t cte_trans_params = {
|
||||
.adv_handle = 0,
|
||||
.cte_len = ESP_BLE_CTE_MAX_CTE_LENGTH,
|
||||
.cte_type = ESP_BLE_CTE_TYPE_AOA,
|
||||
.cte_count = ESP_BLE_CTE_MIN_CTE_COUNT,
|
||||
.switching_pattern_len = sizeof(antenna_ids),
|
||||
.antenna_ids = antenna_ids,
|
||||
};
|
||||
|
||||
static esp_ble_cte_trans_enable_params_t cte_trans_enable = {
|
||||
.adv_handle = 0,
|
||||
.cte_enable = ESP_BLE_CTE_ADV_WITH_CTE_ENABLE,
|
||||
};
|
||||
|
||||
static uint8_t periodic_adv_hdl = 0xff;
|
||||
|
||||
static void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param)
|
||||
{
|
||||
switch (event) {
|
||||
case ESP_GAP_BLE_EXT_ADV_SET_RAND_ADDR_COMPLETE_EVT:
|
||||
xSemaphoreGive(test_sem);
|
||||
ESP_LOGI(LOG_TAG, "ESP_GAP_BLE_EXT_ADV_SET_RAND_ADDR_COMPLETE_EVT, status %d", param->ext_adv_set_rand_addr.status);
|
||||
break;
|
||||
case ESP_GAP_BLE_EXT_ADV_SET_PARAMS_COMPLETE_EVT:
|
||||
xSemaphoreGive(test_sem);
|
||||
ESP_LOGI(LOG_TAG, "ESP_GAP_BLE_EXT_ADV_SET_PARAMS_COMPLETE_EVT, status %d", param->ext_adv_set_params.status);
|
||||
break;
|
||||
case ESP_GAP_BLE_EXT_ADV_DATA_SET_COMPLETE_EVT:
|
||||
xSemaphoreGive(test_sem);
|
||||
ESP_LOGI(LOG_TAG, "ESP_GAP_BLE_EXT_ADV_DATA_SET_COMPLETE_EVT, status %d", param->ext_adv_data_set.status);
|
||||
break;
|
||||
case ESP_GAP_BLE_EXT_SCAN_RSP_DATA_SET_COMPLETE_EVT:
|
||||
xSemaphoreGive(test_sem);
|
||||
ESP_LOGI(LOG_TAG, "ESP_GAP_BLE_EXT_SCAN_RSP_DATA_SET_COMPLETE_EVT, status %d", param->scan_rsp_set.status);
|
||||
break;
|
||||
case ESP_GAP_BLE_EXT_ADV_START_COMPLETE_EVT:
|
||||
xSemaphoreGive(test_sem);
|
||||
ESP_LOGI(LOG_TAG, "ESP_GAP_BLE_EXT_ADV_START_COMPLETE_EVT, status %d", param->ext_adv_start.status);
|
||||
break;
|
||||
case ESP_GAP_BLE_EXT_ADV_STOP_COMPLETE_EVT:
|
||||
xSemaphoreGive(test_sem);
|
||||
ESP_LOGI(LOG_TAG, "ESP_GAP_BLE_EXT_ADV_STOP_COMPLETE_EVT, status %d", param->ext_adv_stop.status);
|
||||
break;
|
||||
case ESP_GAP_BLE_PERIODIC_ADV_SET_PARAMS_COMPLETE_EVT:
|
||||
periodic_adv_hdl = param->peroid_adv_set_params.instance;
|
||||
xSemaphoreGive(test_sem);
|
||||
ESP_LOGI(LOG_TAG, "ESP_GAP_BLE_PERIODIC_ADV_SET_PARAMS_COMPLETE_EVT, status %d", param->peroid_adv_set_params.status);
|
||||
break;
|
||||
case ESP_GAP_BLE_PERIODIC_ADV_DATA_SET_COMPLETE_EVT:
|
||||
xSemaphoreGive(test_sem);
|
||||
ESP_LOGI(LOG_TAG, "ESP_GAP_BLE_PERIODIC_ADV_DATA_SET_COMPLETE_EVT, status %d", param->period_adv_data_set.status);
|
||||
break;
|
||||
case ESP_GAP_BLE_PERIODIC_ADV_START_COMPLETE_EVT:
|
||||
xSemaphoreGive(test_sem);
|
||||
ESP_LOGI(LOG_TAG, "ESP_GAP_BLE_PERIODIC_ADV_START_COMPLETE_EVT, status %d", param->period_adv_start.status);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void cte_event_handler(esp_ble_cte_cb_event_t event, esp_ble_cte_cb_param_t *param)
|
||||
{
|
||||
switch (event) {
|
||||
case ESP_BLE_CTE_SET_CONNLESS_TRANS_PARAMS_CMPL_EVT:
|
||||
xSemaphoreGive(test_sem);
|
||||
ESP_LOGI(LOG_TAG, "ESP_BLE_CTE_SET_CONNLESS_TRANS_PARAMS_CMPL_EVT, status %d", param->set_trans_params_cmpl.status);
|
||||
break;
|
||||
case ESP_BLE_CTE_SET_CONNLESS_TRANS_ENABLE_CMPL_EVT:
|
||||
xSemaphoreGive(test_sem);
|
||||
ESP_LOGI(LOG_TAG, "ESP_BLE_CTE_SET_CONNLESS_TRANS_ENABLE_CMPL_EVT, status %d", param->set_trans_enable_cmpl.status);
|
||||
break;
|
||||
case ESP_BLE_CTE_SET_CONNLESS_IQ_SAMPLING_ENABLE_CMPL_EVT:
|
||||
break;
|
||||
case ESP_BLE_CTE_READ_ANT_INFOR_CMPL_EVT:
|
||||
break;
|
||||
case ESP_BLE_CTE_CONNLESS_IQ_REPORT_EVT:
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
esp_err_t ret;
|
||||
|
||||
// Initialize NVS.
|
||||
ret = nvs_flash_init();
|
||||
if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
|
||||
ESP_ERROR_CHECK(nvs_flash_erase());
|
||||
ret = nvs_flash_init();
|
||||
}
|
||||
ESP_ERROR_CHECK( ret );
|
||||
|
||||
ESP_ERROR_CHECK(esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT));
|
||||
|
||||
esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT();
|
||||
ret = esp_bt_controller_init(&bt_cfg);
|
||||
if (ret) {
|
||||
ESP_LOGE(LOG_TAG, "%s initialize controller failed: %s", __func__, esp_err_to_name(ret));
|
||||
return;
|
||||
}
|
||||
|
||||
ret = esp_bt_controller_enable(ESP_BT_MODE_BLE);
|
||||
if (ret) {
|
||||
ESP_LOGE(LOG_TAG, "%s enable controller failed: %s", __func__, esp_err_to_name(ret));
|
||||
return;
|
||||
}
|
||||
esp_bluedroid_config_t bluedroid_cfg = BT_BLUEDROID_INIT_CONFIG_DEFAULT();
|
||||
ret = esp_bluedroid_init_with_cfg(&bluedroid_cfg);
|
||||
if (ret) {
|
||||
ESP_LOGE(LOG_TAG, "%s init bluetooth failed: %s", __func__, esp_err_to_name(ret));
|
||||
return;
|
||||
}
|
||||
ret = esp_bluedroid_enable();
|
||||
if (ret) {
|
||||
ESP_LOGE(LOG_TAG, "%s enable bluetooth failed: %s", __func__, esp_err_to_name(ret));
|
||||
return;
|
||||
}
|
||||
ret = esp_ble_gap_register_callback(gap_event_handler);
|
||||
if (ret){
|
||||
ESP_LOGE(LOG_TAG, "gap register error, error code = %x", ret);
|
||||
return;
|
||||
}
|
||||
|
||||
esp_ble_cte_register_callback(cte_event_handler);
|
||||
|
||||
vTaskDelay(200 / portTICK_PERIOD_MS);
|
||||
|
||||
test_sem = xSemaphoreCreateBinary();
|
||||
// 2M phy extend adv, Connectable advertising
|
||||
FUNC_SEND_WAIT_SEM(esp_ble_gap_ext_adv_set_params(EXT_ADV_HANDLE, &ext_adv_params_2M), test_sem);
|
||||
FUNC_SEND_WAIT_SEM(esp_ble_gap_ext_adv_set_rand_addr(EXT_ADV_HANDLE, addr_2m), test_sem);
|
||||
FUNC_SEND_WAIT_SEM(esp_ble_gap_config_ext_adv_data_raw(EXT_ADV_HANDLE, sizeof(raw_ext_adv_data_2m), &raw_ext_adv_data_2m[0]), test_sem);
|
||||
|
||||
// start all adv
|
||||
FUNC_SEND_WAIT_SEM(esp_ble_gap_ext_adv_start(NUM_EXT_ADV, &ext_adv[0]), test_sem);
|
||||
|
||||
// set periodic adv param
|
||||
FUNC_SEND_WAIT_SEM(esp_ble_gap_periodic_adv_set_params(EXT_ADV_HANDLE, &periodic_adv_params), test_sem);
|
||||
|
||||
cte_trans_params.adv_handle = periodic_adv_hdl;
|
||||
FUNC_SEND_WAIT_SEM(esp_ble_cte_set_connectionless_trans_params(&cte_trans_params), test_sem);
|
||||
|
||||
cte_trans_enable.adv_handle = periodic_adv_hdl;
|
||||
FUNC_SEND_WAIT_SEM(esp_ble_cte_set_connectionless_trans_enable(&cte_trans_enable), test_sem);
|
||||
|
||||
#if (CONFIG_BT_BLE_FEAT_PERIODIC_ADV_ENH)
|
||||
// set periodic adv raw data
|
||||
FUNC_SEND_WAIT_SEM(esp_ble_gap_config_periodic_adv_data_raw(EXT_ADV_HANDLE, sizeof(periodic_adv_raw_data), &periodic_adv_raw_data[0], false), test_sem);
|
||||
// start periodic adv, include the ADI field in AUX_SYNC_IND PDUs
|
||||
FUNC_SEND_WAIT_SEM(esp_ble_gap_periodic_adv_start(EXT_ADV_HANDLE, true), test_sem);
|
||||
while (1) {
|
||||
vTaskDelay(2000 / portTICK_PERIOD_MS);
|
||||
// just update the Advertising DID of the periodic advertising, unchanged data
|
||||
FUNC_SEND_WAIT_SEM(esp_ble_gap_config_periodic_adv_data_raw(EXT_ADV_HANDLE, 0, NULL, true), test_sem);
|
||||
}
|
||||
#else
|
||||
// set periodic adv raw data
|
||||
FUNC_SEND_WAIT_SEM(esp_ble_gap_config_periodic_adv_data_raw(EXT_ADV_HANDLE, sizeof(periodic_adv_raw_data), &periodic_adv_raw_data[0]), test_sem);
|
||||
FUNC_SEND_WAIT_SEM(esp_ble_gap_periodic_adv_start(EXT_ADV_HANDLE), test_sem);
|
||||
#endif
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
CONFIG_BT_ENABLED=y
|
||||
CONFIG_BT_BLUEDROID_ENABLED=y
|
||||
CONFIG_BT_BLE_50_FEATURES_SUPPORTED=y
|
||||
CONFIG_BT_BLE_42_FEATURES_SUPPORTED=n
|
||||
|
||||
CONFIG_BT_BLE_FEAT_PERIODIC_ADV_ENH=y
|
||||
CONFIG_BT_BLE_FEAT_CTE_EN=y
|
||||
CONFIG_BT_LE_CTE_FEATURE_ENABLED=y
|
||||
|
||||
CONFIG_BT_BLE_FEAT_CTE_CONNECTION_EN=n
|
||||
|
||||
CONFIG_BT_BLE_FEAT_CTE_CONNECTIONLESS_EN=y
|
||||
|
||||
CONFIG_BT_BLE_SMP_ENABLE=n
|
||||
|
||||
CONFIG_BT_GATTC_ENABLE=n
|
||||
|
||||
CONFIG_BT_GATTS_ENABLE=n
|
||||
|
||||
CONFIG_BT_BLE_50_EXTEND_ADV_EN=y
|
||||
|
||||
CONFIG_BT_BLE_50_PERIODIC_ADV_EN=y
|
||||
|
||||
CONFIG_BT_BLE_50_EXTEND_SCAN_EN=n
|
||||
|
||||
CONFIG_BT_BLE_50_DTM_TEST_EN=n
|
||||
@@ -0,0 +1,8 @@
|
||||
# The following lines of boilerplate have to be in your project's CMakeLists
|
||||
# in this exact order for cmake to work correctly
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
# "Trim" the build. Include the minimal set of components, main, and anything it depends on.
|
||||
idf_build_set_property(MINIMAL_BUILD ON)
|
||||
project(periodic_sync_with_cte_demos)
|
||||
@@ -0,0 +1,86 @@
|
||||
| Supported Targets | ESP32-C5 | ESP32-C61 | ESP32-H2 |
|
||||
| ----------------- | -------- | --------- | -------- |
|
||||
|
||||
# ESP-IDF BLE Periodic Sync with CTE Example
|
||||
|
||||
This example demonstrates how to use BLE Periodic Synchronization with Constant Tone Extension (CTE) feature using Bluedroid host API.
|
||||
|
||||
## What is BLE Periodic Sync with CTE?
|
||||
|
||||
BLE Periodic Sync with CTE combines periodic synchronization with Constant Tone Extension to enable Direction Finding (AoA/AoD) functionality when synchronizing with periodic advertising. This allows devices to determine the direction of a Bluetooth signal during periodic advertising synchronization.
|
||||
|
||||
## How It Works
|
||||
|
||||
1. The device starts extended scanning
|
||||
2. The device creates periodic sync with a periodic advertiser
|
||||
3. The device configures CTE receive parameters for periodic sync
|
||||
4. The device receives periodic advertising packets with CTE
|
||||
5. IQ samples are received for direction finding
|
||||
|
||||
## API Usage
|
||||
|
||||
- `esp_ble_gap_ext_scan_start()` - Start extended scanning
|
||||
- `esp_ble_gap_periodic_adv_create_sync()` - Create periodic sync
|
||||
- `esp_ble_cte_set_periodic_adv_receive_params()` - Set CTE receive parameters for periodic sync
|
||||
- `esp_ble_cte_periodic_adv_request_enable()` - Enable CTE request for periodic sync
|
||||
|
||||
## Configuration
|
||||
|
||||
The example requires BLE 5.0 CTE and periodic advertising features to be enabled:
|
||||
- `CONFIG_BT_BLE_50_FEATURES_SUPPORTED=y`
|
||||
- `CONFIG_BT_BLE_50_EXTEND_SCAN_EN=y`
|
||||
- `CONFIG_BT_BLE_50_PERIODIC_ADV_EN=y`
|
||||
- `CONFIG_BT_BLE_FEAT_CTE_EN=y`
|
||||
- `CONFIG_BT_LE_CTE_FEATURE_ENABLED=y`
|
||||
|
||||
To test this demo, you can run the [ble_periodic_adv_with_cte](../ble_periodic_adv_with_cte) example on another device, which starts periodic advertising with CTE.
|
||||
|
||||
## 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
|
||||
|
||||
* A development board with ESP32-C5, ESP32-C61, or ESP32-H2 SoC that supports BLE 5.0 CTE
|
||||
* A USB cable for Power supply and programming
|
||||
|
||||
See [Development Boards](https://www.espressif.com/en/products/devkits) for more information about it.
|
||||
|
||||
### 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
|
||||
|
||||
The example will output periodic sync and CTE-related events including:
|
||||
- Extended scan start completion
|
||||
- Periodic sync creation completion
|
||||
- Periodic sync establishment
|
||||
- Periodic advertising reports
|
||||
- CTE parameter configuration completion
|
||||
- CTE request enable completion
|
||||
- IQ sample reports for direction finding
|
||||
|
||||
```
|
||||
I (xxx) CTE_TEST: Extended scan started
|
||||
I (xxx) CTE_TEST: Periodic sync created
|
||||
I (xxx) CTE_TEST: Periodic sync established
|
||||
I (xxx) CTE_TEST: Periodic adv report received
|
||||
I (xxx) CTE_TEST: CTE set periodic adv receive params, status 0
|
||||
I (xxx) CTE_TEST: CTE periodic adv request enable, status 0
|
||||
I (xxx) CTE_TEST: CTE periodic IQ report received
|
||||
...
|
||||
```
|
||||
|
||||
## 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 "periodic_sync_with_cte_demo.c"
|
||||
PRIV_REQUIRES bt nvs_flash
|
||||
INCLUDE_DIRS ".")
|
||||
@@ -0,0 +1,246 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Unlicense OR CC0-1.0
|
||||
*/
|
||||
|
||||
/****************************************************************************
|
||||
*
|
||||
* This demo showcases BLE GATT server. It can send adv data, be connected by client.
|
||||
* Run the gatt_client demo, the client demo will automatically connect to the gatt_server demo.
|
||||
* Client demo will enable gatt_server's notify after connection. The two devices will then exchange
|
||||
* data.
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "freertos/event_groups.h"
|
||||
#include "esp_system.h"
|
||||
#include "esp_log.h"
|
||||
#include "nvs_flash.h"
|
||||
#include "esp_bt.h"
|
||||
|
||||
#include "esp_gap_ble_api.h"
|
||||
#include "esp_gatts_api.h"
|
||||
#include "esp_bt_defs.h"
|
||||
#include "esp_bt_main.h"
|
||||
#include "esp_gatt_common_api.h"
|
||||
|
||||
#include "sdkconfig.h"
|
||||
|
||||
#include "freertos/semphr.h"
|
||||
#include "esp_ble_cte_api.h"
|
||||
|
||||
|
||||
#define FUNC_SEND_WAIT_SEM(func, sem) do {\
|
||||
esp_err_t __err_rc = (func);\
|
||||
if (__err_rc != ESP_OK) { \
|
||||
ESP_LOGE(LOG_TAG, "%s, message send fail, error = %d", __func__, __err_rc); \
|
||||
} \
|
||||
xSemaphoreTake(sem, portMAX_DELAY); \
|
||||
} while(0);
|
||||
|
||||
#define LOG_TAG "PERIODIC_SYNC"
|
||||
#define EXT_SCAN_DURATION 0
|
||||
#define EXT_SCAN_PERIOD 0
|
||||
|
||||
static char remote_device_name[ESP_BLE_ADV_NAME_LEN_MAX] = "ESP_CTE_BLUEDROID";
|
||||
static SemaphoreHandle_t test_sem = NULL;
|
||||
|
||||
static esp_ble_ext_scan_params_t ext_scan_params = {
|
||||
.own_addr_type = BLE_ADDR_TYPE_PUBLIC,
|
||||
.filter_policy = BLE_SCAN_FILTER_ALLOW_ALL,
|
||||
.scan_duplicate = BLE_SCAN_DUPLICATE_ENABLE,
|
||||
.cfg_mask = ESP_BLE_GAP_EXT_SCAN_CFG_UNCODE_MASK | ESP_BLE_GAP_EXT_SCAN_CFG_CODE_MASK,
|
||||
.uncoded_cfg = {BLE_SCAN_TYPE_ACTIVE, 40, 40},
|
||||
.coded_cfg = {BLE_SCAN_TYPE_ACTIVE, 40, 40},
|
||||
};
|
||||
|
||||
static esp_ble_gap_periodic_adv_sync_params_t periodic_adv_sync_params = {
|
||||
.filter_policy = 0,
|
||||
.sid = 0,
|
||||
.addr_type = BLE_ADDR_TYPE_RANDOM,
|
||||
.skip = 10,
|
||||
.sync_timeout = 1000,
|
||||
};
|
||||
|
||||
uint8_t antenna_ids[4] = {0x00, 0x04, 0x08, 0x0D};
|
||||
static esp_ble_cte_iq_sampling_params_t iq_sampling_en = {
|
||||
.sync_handle = 0,
|
||||
.sampling_en = ESP_BLE_CTE_SAMPLING_ENABLE,
|
||||
.slot_dur = ESP_BLE_CTE_SLOT_DURATION_2US,
|
||||
.max_sampled_ctes = ESP_BLE_CTE_MIN_SAMPLED_CTES,
|
||||
.switching_pattern_len = sizeof(antenna_ids),
|
||||
.antenna_ids = &antenna_ids[0],
|
||||
};
|
||||
|
||||
bool periodic_sync = false;
|
||||
|
||||
|
||||
static void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param)
|
||||
{
|
||||
switch (event) {
|
||||
case ESP_GAP_BLE_SET_EXT_SCAN_PARAMS_COMPLETE_EVT:
|
||||
xSemaphoreGive(test_sem);
|
||||
ESP_LOGI(LOG_TAG, "Extended scanning params set, status %d", param->set_ext_scan_params.status);
|
||||
break;
|
||||
case ESP_GAP_BLE_EXT_SCAN_START_COMPLETE_EVT:
|
||||
xSemaphoreGive(test_sem);
|
||||
ESP_LOGI(LOG_TAG, "Extended scanning start, status %d", param->ext_scan_start.status);
|
||||
break;
|
||||
case ESP_GAP_BLE_EXT_SCAN_STOP_COMPLETE_EVT:
|
||||
xSemaphoreGive(test_sem);
|
||||
ESP_LOGI(LOG_TAG, "Extended scanning stop, status %d", param->ext_scan_stop.status);
|
||||
break;
|
||||
case ESP_GAP_BLE_PERIODIC_ADV_CREATE_SYNC_COMPLETE_EVT:
|
||||
ESP_LOGI(LOG_TAG, "Periodic advertising create sync, status %d", param->period_adv_create_sync.status);
|
||||
break;
|
||||
case ESP_GAP_BLE_PERIODIC_ADV_SYNC_CANCEL_COMPLETE_EVT:
|
||||
ESP_LOGI(LOG_TAG, "Periodic advertising sync cancel, status %d", param->period_adv_sync_cancel.status);
|
||||
break;
|
||||
case ESP_GAP_BLE_PERIODIC_ADV_SYNC_TERMINATE_COMPLETE_EVT:
|
||||
ESP_LOGI(LOG_TAG, "Periodic advertising sync terminate, status %d", param->period_adv_sync_term.status);
|
||||
break;
|
||||
case ESP_GAP_BLE_PERIODIC_ADV_SYNC_LOST_EVT:
|
||||
ESP_LOGI(LOG_TAG, "Periodic advertising sync lost, sync handle %d", param->periodic_adv_sync_lost.sync_handle);
|
||||
break;
|
||||
case ESP_GAP_BLE_PERIODIC_ADV_SYNC_ESTAB_EVT:
|
||||
ESP_LOGI(LOG_TAG, "Periodic advertising sync establish, status %d", param->periodic_adv_sync_estab.status);
|
||||
ESP_LOGI(LOG_TAG, "address "ESP_BD_ADDR_STR"", ESP_BD_ADDR_HEX(param->periodic_adv_sync_estab.adv_addr));
|
||||
ESP_LOGI(LOG_TAG, "sync handle %d sid %d perioic adv interval %d adv phy %d", param->periodic_adv_sync_estab.sync_handle,
|
||||
param->periodic_adv_sync_estab.sid,
|
||||
param->periodic_adv_sync_estab.period_adv_interval,
|
||||
param->periodic_adv_sync_estab.adv_phy);
|
||||
iq_sampling_en.sync_handle = param->periodic_adv_sync_estab.sync_handle;
|
||||
esp_ble_cte_set_connectionless_iq_sampling_enable(&iq_sampling_en);
|
||||
break;
|
||||
case ESP_GAP_BLE_EXT_ADV_REPORT_EVT: {
|
||||
uint8_t *adv_name = NULL;
|
||||
uint8_t adv_name_len = 0;
|
||||
adv_name = esp_ble_resolve_adv_data_by_type(param->ext_adv_report.params.adv_data,
|
||||
param->ext_adv_report.params.adv_data_len,
|
||||
ESP_BLE_AD_TYPE_NAME_CMPL,
|
||||
&adv_name_len);
|
||||
if ((adv_name != NULL) && (memcmp(adv_name, remote_device_name, adv_name_len) == 0) && !periodic_sync) {
|
||||
// Note: If there are multiple devices with the same device name, the device may sync to an unintended one.
|
||||
// It is recommended to change the default device name to ensure it is unique.
|
||||
periodic_sync = true;
|
||||
char adv_temp_name[30] = {'0'};
|
||||
memcpy(adv_temp_name, adv_name, adv_name_len);
|
||||
ESP_LOGI(LOG_TAG, "Create sync with the peer device %s", adv_temp_name);
|
||||
periodic_adv_sync_params.sid = param->ext_adv_report.params.sid;
|
||||
periodic_adv_sync_params.addr_type = param->ext_adv_report.params.addr_type;
|
||||
memcpy(periodic_adv_sync_params.addr, param->ext_adv_report.params.addr, sizeof(esp_bd_addr_t));
|
||||
esp_ble_gap_periodic_adv_create_sync(&periodic_adv_sync_params);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case ESP_GAP_BLE_PERIODIC_ADV_REPORT_EVT:
|
||||
ESP_LOGI(LOG_TAG, "Periodic adv report, sync handle %d, data status %d, data len %d, rssi %d", param->period_adv_report.params.sync_handle,
|
||||
param->period_adv_report.params.data_status,
|
||||
param->period_adv_report.params.data_length,
|
||||
param->period_adv_report.params.rssi);
|
||||
//esp_log_buffer_hex("data", param->period_adv_report.params.data, param->period_adv_report.params.data_length);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void cte_event_handler(esp_ble_cte_cb_event_t event, esp_ble_cte_cb_param_t *param)
|
||||
{
|
||||
switch (event) {
|
||||
case ESP_BLE_CTE_SET_CONNLESS_TRANS_PARAMS_CMPL_EVT:
|
||||
ESP_LOGI(LOG_TAG, "ESP_BLE_CTE_SET_CONNLESS_TRANS_PARAMS_CMPL_EVT, status %d", param->set_trans_params_cmpl.status);
|
||||
break;
|
||||
case ESP_BLE_CTE_SET_CONNLESS_TRANS_ENABLE_CMPL_EVT:
|
||||
ESP_LOGI(LOG_TAG, "ESP_BLE_CTE_SET_CONNLESS_TRANS_ENABLE_CMPL_EVT, status %d", param->set_trans_enable_cmpl.status);
|
||||
break;
|
||||
case ESP_BLE_CTE_SET_CONNLESS_IQ_SAMPLING_ENABLE_CMPL_EVT:
|
||||
ESP_LOGI(LOG_TAG, "CTE IQ sampling enable, status %d", param->iq_sampling_enable_cmpl.status);
|
||||
break;
|
||||
case ESP_BLE_CTE_READ_ANT_INFOR_CMPL_EVT:
|
||||
ESP_LOGI(LOG_TAG, "CTE read ant information cmpl, status %d", param->read_ant_infor_cmpl.status);
|
||||
break;
|
||||
case ESP_BLE_CTE_CONNLESS_IQ_REPORT_EVT:
|
||||
ESP_LOGI(LOG_TAG, "sync_handle %d channel_idx %d rssi %d rssi_ant_id %d cte_type %d slot_dur %d pkt_status %d periodic_evt_counter %d sample_count %d",
|
||||
param->connless_iq_rpt.sync_handle, param->connless_iq_rpt.channel_idx, param->connless_iq_rpt.rssi,
|
||||
param->connless_iq_rpt.rssi_ant_id, param->connless_iq_rpt.cte_type, param->connless_iq_rpt.slot_dur,
|
||||
param->connless_iq_rpt.pkt_status, param->connless_iq_rpt.periodic_evt_counter, param->connless_iq_rpt.sample_count);
|
||||
|
||||
ESP_LOG_BUFFER_HEX("i_sample", ¶m->connless_iq_rpt.i_sample[0], param->connless_iq_rpt.sample_count);
|
||||
ESP_LOG_BUFFER_HEX("q_sample", ¶m->connless_iq_rpt.q_sample[0], param->connless_iq_rpt.sample_count);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
esp_err_t ret;
|
||||
|
||||
// Initialize NVS.
|
||||
ret = nvs_flash_init();
|
||||
if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
|
||||
ESP_ERROR_CHECK(nvs_flash_erase());
|
||||
ret = nvs_flash_init();
|
||||
}
|
||||
ESP_ERROR_CHECK( ret );
|
||||
|
||||
#if CONFIG_EXAMPLE_CI_PIPELINE_ID
|
||||
memcpy(remote_device_name, esp_bluedroid_get_example_name(), sizeof(remote_device_name));
|
||||
#endif
|
||||
|
||||
ESP_ERROR_CHECK(esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT));
|
||||
|
||||
esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT();
|
||||
ret = esp_bt_controller_init(&bt_cfg);
|
||||
if (ret) {
|
||||
ESP_LOGE(LOG_TAG, "%s initialize controller failed: %s", __func__, esp_err_to_name(ret));
|
||||
return;
|
||||
}
|
||||
|
||||
ret = esp_bt_controller_enable(ESP_BT_MODE_BLE);
|
||||
if (ret) {
|
||||
ESP_LOGE(LOG_TAG, "%s enable controller failed: %s", __func__, esp_err_to_name(ret));
|
||||
return;
|
||||
}
|
||||
|
||||
esp_bluedroid_config_t cfg = BT_BLUEDROID_INIT_CONFIG_DEFAULT();
|
||||
ret = esp_bluedroid_init_with_cfg(&cfg);
|
||||
if (ret) {
|
||||
ESP_LOGE(LOG_TAG, "%s init bluetooth failed: %s", __func__, esp_err_to_name(ret));
|
||||
return;
|
||||
}
|
||||
ret = esp_bluedroid_enable();
|
||||
if (ret) {
|
||||
ESP_LOGE(LOG_TAG, "%s enable bluetooth failed: %s", __func__, esp_err_to_name(ret));
|
||||
return;
|
||||
}
|
||||
ret = esp_ble_gap_register_callback(gap_event_handler);
|
||||
if (ret){
|
||||
ESP_LOGE(LOG_TAG, "gap register error, error code = %x", ret);
|
||||
return;
|
||||
}
|
||||
|
||||
esp_ble_cte_register_callback(cte_event_handler);
|
||||
|
||||
vTaskDelay(200 / portTICK_PERIOD_MS);
|
||||
|
||||
test_sem = xSemaphoreCreateBinary();
|
||||
|
||||
FUNC_SEND_WAIT_SEM(esp_ble_gap_set_ext_scan_params(&ext_scan_params), test_sem);
|
||||
FUNC_SEND_WAIT_SEM(esp_ble_gap_start_ext_scan(EXT_SCAN_DURATION, EXT_SCAN_PERIOD), test_sem);
|
||||
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
CONFIG_BT_ENABLED=y
|
||||
CONFIG_BT_BLUEDROID_ENABLED=y
|
||||
CONFIG_BT_BLE_50_FEATURES_SUPPORTED=y
|
||||
CONFIG_BT_BLE_42_FEATURES_SUPPORTED=n
|
||||
|
||||
CONFIG_BT_BLE_FEAT_PERIODIC_ADV_ENH=y
|
||||
CONFIG_BT_BLE_FEAT_CTE_EN=y
|
||||
CONFIG_BT_LE_CTE_FEATURE_ENABLED=y
|
||||
|
||||
CONFIG_BT_BLE_FEAT_CTE_CONNECTION_EN=n
|
||||
|
||||
CONFIG_BT_BLE_FEAT_CTE_CONNECTIONLESS_EN=y
|
||||
|
||||
CONFIG_BT_BLE_SMP_ENABLE=n
|
||||
|
||||
CONFIG_BT_GATTC_ENABLE=n
|
||||
|
||||
CONFIG_BT_GATTS_ENABLE=n
|
||||
|
||||
CONFIG_BT_BLE_50_EXTEND_ADV_EN=n
|
||||
|
||||
CONFIG_BT_BLE_50_EXTEND_SCAN_EN=y
|
||||
|
||||
CONFIG_BT_BLE_50_DTM_TEST_EN=n
|
||||
@@ -0,0 +1,8 @@
|
||||
# The following lines of boilerplate have to be in your project's CMakeLists
|
||||
# in this exact order for cmake to work correctly
|
||||
cmake_minimum_required(VERSION 3.22)
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
# "Trim" the build. Include the minimal set of components, main, and anything it depends on.
|
||||
idf_build_set_property(MINIMAL_BUILD ON)
|
||||
project(ble_power_control_central)
|
||||
@@ -0,0 +1,54 @@
|
||||
| Supported Targets | ESP32-C5 | ESP32-C6 | ESP32-C61 | ESP32-H2 |
|
||||
| ----------------- | -------- | -------- | --------- | -------- |
|
||||
|
||||
# BLE Power Control Central Example
|
||||
|
||||
This example demonstrates how to use BLE Power Control feature as a Central device using Bluedroid host API.
|
||||
|
||||
## What is BLE Power Control?
|
||||
|
||||
BLE Power Control is a BLE 5.0 feature that allows devices to monitor and control transmit power levels, and report path loss between connected devices. This helps optimize power consumption and maintain connection quality.
|
||||
|
||||
## How It Works
|
||||
|
||||
1. The Central device scans for BLE devices using extended scanning
|
||||
2. When it finds the target device (Peripheral), it connects to it
|
||||
3. After connection, the Central reads transmit power levels (local and remote)
|
||||
4. The Central enables transmit power reporting and path loss reporting
|
||||
5. The Central receives power and path loss events when conditions change
|
||||
|
||||
## API Usage
|
||||
|
||||
- `esp_ble_gap_enhanced_read_transmit_power_level()` - Read local transmit power levels
|
||||
- `esp_ble_gap_read_remote_transmit_power_level()` - Read remote transmit power level
|
||||
- `esp_ble_gap_set_transmit_power_reporting_enable()` - Enable transmit power reporting
|
||||
- `esp_ble_gap_set_path_loss_reporting_params()` - Set path loss reporting parameters
|
||||
- `esp_ble_gap_set_path_loss_reporting_enable()` - Enable path loss reporting
|
||||
- `ESP_GAP_BLE_TRANS_PWR_RPTING_EVT` - Event received when transmit power changes
|
||||
- `ESP_GAP_BLE_PATH_LOSS_THRESHOLD_EVT` - Event received when path loss crosses threshold
|
||||
|
||||
## Configuration
|
||||
|
||||
The example requires BLE 5.0 features to be enabled:
|
||||
- `CONFIG_BT_BLE_50_FEATURES_SUPPORTED=y`
|
||||
- `CONFIG_BT_BLE_50_EXTEND_SCAN_EN=y`
|
||||
- `CONFIG_BT_BLE_FEAT_POWER_CONTROL=y`
|
||||
|
||||
## Hardware Required
|
||||
|
||||
- One ESP32 development board (ESP32-C5, ESP32-C6, ESP32-H2, etc.) that supports BLE 5.0
|
||||
|
||||
## Build and Flash
|
||||
|
||||
```bash
|
||||
idf.py set-target esp32c5
|
||||
idf.py build flash monitor
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
1. Flash this example to one ESP32 device (Central)
|
||||
2. Flash the `ble_power_control_peripheral` example to another ESP32 device (Peripheral)
|
||||
3. The Central will automatically scan, connect, and monitor power levels
|
||||
4. Monitor the serial output to see power and path loss events
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
idf_component_register(SRCS "main.c"
|
||||
PRIV_REQUIRES bt nvs_flash
|
||||
INCLUDE_DIRS ".")
|
||||
@@ -0,0 +1,369 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Unlicense OR CC0-1.0
|
||||
*/
|
||||
|
||||
/****************************************************************************
|
||||
*
|
||||
* This demo showcases BLE Power Control feature on Central side.
|
||||
* It scans for BLE devices and connects to a peripheral device. After connection,
|
||||
* it reads transmit power levels, sets path loss reporting parameters, and
|
||||
* enables power reporting to monitor and control power consumption.
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include "nvs.h"
|
||||
#include "nvs_flash.h"
|
||||
|
||||
#include "esp_bt.h"
|
||||
#include "esp_gap_ble_api.h"
|
||||
#include "esp_gattc_api.h"
|
||||
#include "esp_gatt_defs.h"
|
||||
#include "esp_bt_main.h"
|
||||
#include "esp_gatt_common_api.h"
|
||||
#include "esp_log.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
|
||||
#define TAG "BLE_POWER_CONTROL_CENTRAL"
|
||||
#define REMOTE_DEVICE_NAME "ESP_PWR_CTL_PRH"
|
||||
#define PROFILE_NUM 1
|
||||
#define PROFILE_A_APP_ID 0
|
||||
|
||||
static bool connect = false;
|
||||
static uint16_t conn_handle = 0xFFFF;
|
||||
|
||||
static esp_ble_ext_scan_params_t ext_scan_params = {
|
||||
.own_addr_type = BLE_ADDR_TYPE_PUBLIC,
|
||||
.filter_policy = BLE_SCAN_FILTER_ALLOW_ALL,
|
||||
.scan_duplicate = BLE_SCAN_DUPLICATE_DISABLE,
|
||||
.cfg_mask = ESP_BLE_GAP_EXT_SCAN_CFG_UNCODE_MASK,
|
||||
.uncoded_cfg = {BLE_SCAN_TYPE_ACTIVE, 0x40, 0x40},
|
||||
.coded_cfg = {BLE_SCAN_TYPE_ACTIVE, 0x40, 0x40},
|
||||
};
|
||||
|
||||
// Connection parameters for different PHYs
|
||||
static const esp_ble_conn_params_t phy_1m_conn_params = {
|
||||
.scan_interval = 0x40,
|
||||
.scan_window = 0x40,
|
||||
.interval_min = 0x18,
|
||||
.interval_max = 0x28,
|
||||
.latency = 0,
|
||||
.supervision_timeout = 0x1F4,
|
||||
.min_ce_len = 0,
|
||||
.max_ce_len = 0,
|
||||
};
|
||||
|
||||
static const esp_ble_conn_params_t phy_2m_conn_params = {
|
||||
.scan_interval = 0x40,
|
||||
.scan_window = 0x40,
|
||||
.interval_min = 0x18,
|
||||
.interval_max = 0x28,
|
||||
.latency = 0,
|
||||
.supervision_timeout = 0x1F4,
|
||||
.min_ce_len = 0,
|
||||
.max_ce_len = 0,
|
||||
};
|
||||
|
||||
static const esp_ble_conn_params_t phy_coded_conn_params = {
|
||||
.scan_interval = 0x40,
|
||||
.scan_window = 0x40,
|
||||
.interval_min = 0x18,
|
||||
.interval_max = 0x28,
|
||||
.latency = 0,
|
||||
.supervision_timeout = 0x1F4,
|
||||
.min_ce_len = 0,
|
||||
.max_ce_len = 0,
|
||||
};
|
||||
|
||||
struct gattc_profile_inst {
|
||||
esp_gattc_cb_t gattc_cb;
|
||||
uint16_t gattc_if;
|
||||
uint16_t app_id;
|
||||
uint16_t conn_id;
|
||||
esp_bd_addr_t remote_bda;
|
||||
};
|
||||
|
||||
static struct gattc_profile_inst gl_profile_tab[PROFILE_NUM] = {
|
||||
[PROFILE_A_APP_ID] = {
|
||||
.gattc_cb = NULL,
|
||||
.gattc_if = ESP_GATT_IF_NONE,
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Initialize power control features after connection
|
||||
*/
|
||||
static void init_power_control(uint16_t conn_handle)
|
||||
{
|
||||
if (conn_handle == 0xFFFF) {
|
||||
ESP_LOGE(TAG, "Invalid connection handle");
|
||||
return;
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "Initializing power control features...");
|
||||
|
||||
// Read local transmit power level (LE 1M PHY)
|
||||
esp_err_t ret = esp_ble_gap_enhanced_read_transmit_power_level(conn_handle, ESP_BLE_CONN_TX_POWER_PHY_1M);
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to read local transmit power level, error = 0x%x", ret);
|
||||
} else {
|
||||
ESP_LOGI(TAG, "Read local transmit power level request sent");
|
||||
}
|
||||
|
||||
// Read remote transmit power level (LE 1M PHY)
|
||||
ret = esp_ble_gap_read_remote_transmit_power_level(conn_handle, ESP_BLE_CONN_TX_POWER_PHY_1M);
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to read remote transmit power level, error = 0x%x", ret);
|
||||
} else {
|
||||
ESP_LOGI(TAG, "Read remote transmit power level request sent");
|
||||
}
|
||||
|
||||
// Enable transmit power reporting (both local and remote)
|
||||
ret = esp_ble_gap_set_transmit_power_reporting_enable(conn_handle, true, true);
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to enable transmit power reporting, error = 0x%x", ret);
|
||||
} else {
|
||||
ESP_LOGI(TAG, "Transmit power reporting enabled");
|
||||
}
|
||||
|
||||
// Set path loss reporting parameters
|
||||
esp_ble_path_loss_rpt_params_t path_loss_params = {
|
||||
.conn_handle = conn_handle,
|
||||
.high_threshold = 60, // High threshold in dB
|
||||
.high_hysteresis = 10, // High hysteresis in dB
|
||||
.low_threshold = 30, // Low threshold in dB
|
||||
.low_hysteresis = 10, // Low hysteresis in dB
|
||||
.min_time_spent = 2, // Minimum time spent in dB
|
||||
};
|
||||
ret = esp_ble_gap_set_path_loss_reporting_params(&path_loss_params);
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to set path loss reporting parameters, error = 0x%x", ret);
|
||||
} else {
|
||||
ESP_LOGI(TAG, "Path loss reporting parameters set");
|
||||
}
|
||||
|
||||
// Enable path loss reporting
|
||||
ret = esp_ble_gap_set_path_loss_reporting_enable(conn_handle, true);
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to enable path loss reporting, error = 0x%x", ret);
|
||||
} else {
|
||||
ESP_LOGI(TAG, "Path loss reporting enabled");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief GATT client event handler
|
||||
*/
|
||||
static void gattc_profile_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param)
|
||||
{
|
||||
if (param == NULL) {
|
||||
ESP_LOGE(TAG, "GATTC event handler received NULL param");
|
||||
return;
|
||||
}
|
||||
|
||||
esp_ble_gattc_cb_param_t *p_data = (esp_ble_gattc_cb_param_t *)param;
|
||||
|
||||
switch (event) {
|
||||
case ESP_GATTC_REG_EVT:
|
||||
ESP_LOGI(TAG, "GATT client register, status %d, app_id %d, gattc_if %d",
|
||||
p_data->reg.status, p_data->reg.app_id, gattc_if);
|
||||
gl_profile_tab[PROFILE_A_APP_ID].gattc_if = gattc_if;
|
||||
esp_err_t scan_ret = esp_ble_gap_set_ext_scan_params(&ext_scan_params);
|
||||
if (scan_ret) {
|
||||
ESP_LOGE(TAG, "set ext scan params error, error code = %x", scan_ret);
|
||||
}
|
||||
break;
|
||||
case ESP_GATTC_CONNECT_EVT:
|
||||
ESP_LOGI(TAG, "Connected, conn_id %d, remote "ESP_BD_ADDR_STR"",
|
||||
p_data->connect.conn_id, ESP_BD_ADDR_HEX(p_data->connect.remote_bda));
|
||||
gl_profile_tab[PROFILE_A_APP_ID].conn_id = p_data->connect.conn_id;
|
||||
memcpy(gl_profile_tab[PROFILE_A_APP_ID].remote_bda, p_data->connect.remote_bda, sizeof(esp_bd_addr_t));
|
||||
conn_handle = p_data->connect.conn_id;
|
||||
// Initialize power control after connection
|
||||
init_power_control(conn_handle);
|
||||
break;
|
||||
case ESP_GATTC_DISCONNECT_EVT:
|
||||
connect = false;
|
||||
conn_handle = 0xFFFF;
|
||||
ESP_LOGI(TAG, "Disconnected, remote "ESP_BD_ADDR_STR", reason 0x%02x",
|
||||
ESP_BD_ADDR_HEX(p_data->disconnect.remote_bda), p_data->disconnect.reason);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief GAP event handler
|
||||
*/
|
||||
static void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param)
|
||||
{
|
||||
if (param == NULL) {
|
||||
ESP_LOGE(TAG, "GAP event handler received NULL param");
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t *adv_name = NULL;
|
||||
uint8_t adv_name_len = 0;
|
||||
|
||||
switch (event) {
|
||||
case ESP_GAP_BLE_SET_EXT_SCAN_PARAMS_COMPLETE_EVT:
|
||||
ESP_LOGI(TAG, "Extended scan parameters set, starting scan...");
|
||||
esp_ble_gap_start_ext_scan(0, 0); // Scan indefinitely
|
||||
break;
|
||||
case ESP_GAP_BLE_EXT_SCAN_START_COMPLETE_EVT:
|
||||
if (param->ext_scan_start.status != ESP_BT_STATUS_SUCCESS) {
|
||||
ESP_LOGE(TAG, "Extended scan start failed, status %x", param->ext_scan_start.status);
|
||||
break;
|
||||
}
|
||||
ESP_LOGI(TAG, "Extended scan started successfully");
|
||||
break;
|
||||
case ESP_GAP_BLE_EXT_ADV_REPORT_EVT: {
|
||||
adv_name = esp_ble_resolve_adv_data_by_type(param->ext_adv_report.params.adv_data,
|
||||
param->ext_adv_report.params.adv_data_len,
|
||||
ESP_BLE_AD_TYPE_NAME_CMPL,
|
||||
&adv_name_len);
|
||||
if (adv_name != NULL && adv_name_len > 0) {
|
||||
ESP_LOGI(TAG, "Found device: "ESP_BD_ADDR_STR", name: %.*s",
|
||||
ESP_BD_ADDR_HEX(param->ext_adv_report.params.addr), adv_name_len, adv_name);
|
||||
|
||||
if (adv_name_len == strlen(REMOTE_DEVICE_NAME) &&
|
||||
strncmp((char *)adv_name, REMOTE_DEVICE_NAME, adv_name_len) == 0) {
|
||||
ESP_LOGI(TAG, "Target device found, connecting...");
|
||||
if (!connect) {
|
||||
connect = true;
|
||||
esp_ble_gap_stop_ext_scan();
|
||||
esp_ble_gatt_creat_conn_params_t creat_conn_params = {0};
|
||||
memcpy(&creat_conn_params.remote_bda, param->ext_adv_report.params.addr, ESP_BD_ADDR_LEN);
|
||||
creat_conn_params.remote_addr_type = param->ext_adv_report.params.addr_type;
|
||||
creat_conn_params.own_addr_type = BLE_ADDR_TYPE_PUBLIC;
|
||||
creat_conn_params.is_direct = true;
|
||||
creat_conn_params.is_aux = true;
|
||||
creat_conn_params.phy_mask = ESP_BLE_PHY_1M_PREF_MASK | ESP_BLE_PHY_2M_PREF_MASK | ESP_BLE_PHY_CODED_PREF_MASK;
|
||||
creat_conn_params.phy_1m_conn_params = &phy_1m_conn_params;
|
||||
creat_conn_params.phy_2m_conn_params = &phy_2m_conn_params;
|
||||
creat_conn_params.phy_coded_conn_params = &phy_coded_conn_params;
|
||||
esp_ble_gattc_enh_open(gl_profile_tab[PROFILE_A_APP_ID].gattc_if, &creat_conn_params);
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ESP_GAP_BLE_EXT_SCAN_STOP_COMPLETE_EVT:
|
||||
ESP_LOGI(TAG, "Extended scan stopped");
|
||||
break;
|
||||
case ESP_GAP_BLE_ENH_READ_TRANS_PWR_LEVEL_EVT:
|
||||
ESP_LOGI(TAG, "Enhanced read transmit power level: conn_handle=%d, status=%d, phy=%d, cur_tx_pwr=%d, max_tx_pwr=%d",
|
||||
param->enh_trans_pwr_level_cmpl.conn_handle,
|
||||
param->enh_trans_pwr_level_cmpl.status,
|
||||
param->enh_trans_pwr_level_cmpl.phy,
|
||||
param->enh_trans_pwr_level_cmpl.cur_tx_pwr_level,
|
||||
param->enh_trans_pwr_level_cmpl.max_tx_pwr_level);
|
||||
break;
|
||||
case ESP_GAP_BLE_READ_REMOTE_TRANS_PWR_LEVEL_EVT:
|
||||
ESP_LOGI(TAG, "Read remote transmit power level complete, status %d", param->read_remote_trans_pwr_level_cmpl.status);
|
||||
break;
|
||||
case ESP_GAP_BLE_SET_PATH_LOSS_RPTING_PARAMS_EVT:
|
||||
ESP_LOGI(TAG, "Set path loss reporting parameters complete, status %d, conn_handle=%d",
|
||||
param->set_path_loss_rpting_params.status,
|
||||
param->set_path_loss_rpting_params.conn_handle);
|
||||
break;
|
||||
case ESP_GAP_BLE_SET_PATH_LOSS_RPTING_ENABLE_EVT:
|
||||
ESP_LOGI(TAG, "Set path loss reporting enable complete, status %d, conn_handle=%d",
|
||||
param->set_path_loss_rpting_enable.status,
|
||||
param->set_path_loss_rpting_enable.conn_handle);
|
||||
break;
|
||||
case ESP_GAP_BLE_SET_TRANS_PWR_RPTING_ENABLE_EVT:
|
||||
ESP_LOGI(TAG, "Set transmit power reporting enable complete, status %d, conn_handle=%d",
|
||||
param->set_trans_pwr_rpting_enable.status,
|
||||
param->set_trans_pwr_rpting_enable.conn_handle);
|
||||
break;
|
||||
case ESP_GAP_BLE_PATH_LOSS_THRESHOLD_EVT:
|
||||
ESP_LOGI(TAG, "Path loss threshold event: conn_handle=%d, cur_path_loss=%d, zone_entered=%d",
|
||||
param->path_loss_thres_evt.conn_handle,
|
||||
param->path_loss_thres_evt.cur_path_loss,
|
||||
param->path_loss_thres_evt.zone_entered);
|
||||
break;
|
||||
case ESP_GAP_BLE_TRANS_PWR_RPTING_EVT:
|
||||
ESP_LOGI(TAG, "Transmit power reporting event: conn_handle=%d, status=%d, reason=%d, phy=%d, power_level=%d, power_level_flag=%d, delta=%d",
|
||||
param->trans_power_report_evt.conn_handle,
|
||||
param->trans_power_report_evt.status,
|
||||
param->trans_power_report_evt.reason,
|
||||
param->trans_power_report_evt.phy,
|
||||
param->trans_power_report_evt.tx_power_level,
|
||||
param->trans_power_report_evt.tx_power_level_flag,
|
||||
param->trans_power_report_evt.delta);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
esp_err_t ret;
|
||||
|
||||
// Initialize NVS
|
||||
ret = nvs_flash_init();
|
||||
if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
|
||||
ESP_ERROR_CHECK(nvs_flash_erase());
|
||||
ret = nvs_flash_init();
|
||||
}
|
||||
ESP_ERROR_CHECK(ret);
|
||||
|
||||
// Initialize Bluetooth Controller
|
||||
ESP_ERROR_CHECK(esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT));
|
||||
esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT();
|
||||
ret = esp_bt_controller_init(&bt_cfg);
|
||||
if (ret) {
|
||||
ESP_LOGE(TAG, "Bluetooth controller init failed, error = %x", ret);
|
||||
return;
|
||||
}
|
||||
|
||||
ret = esp_bt_controller_enable(ESP_BT_MODE_BLE);
|
||||
if (ret) {
|
||||
ESP_LOGE(TAG, "Bluetooth controller enable failed, error = %x", ret);
|
||||
return;
|
||||
}
|
||||
|
||||
// Initialize Bluedroid
|
||||
esp_bluedroid_config_t cfg = BT_BLUEDROID_INIT_CONFIG_DEFAULT();
|
||||
ret = esp_bluedroid_init_with_cfg(&cfg);
|
||||
if (ret) {
|
||||
ESP_LOGE(TAG, "Bluedroid init failed, error = %x", ret);
|
||||
return;
|
||||
}
|
||||
|
||||
ret = esp_bluedroid_enable();
|
||||
if (ret) {
|
||||
ESP_LOGE(TAG, "Bluedroid enable failed, error = %x", ret);
|
||||
return;
|
||||
}
|
||||
|
||||
// Register callbacks
|
||||
ret = esp_ble_gap_register_callback(gap_event_handler);
|
||||
if (ret) {
|
||||
ESP_LOGE(TAG, "GAP register callback failed, error = %x", ret);
|
||||
return;
|
||||
}
|
||||
|
||||
ret = esp_ble_gattc_register_callback(gattc_profile_event_handler);
|
||||
if (ret) {
|
||||
ESP_LOGE(TAG, "GATTC register callback failed, error = %x", ret);
|
||||
return;
|
||||
}
|
||||
|
||||
ret = esp_ble_gattc_app_register(PROFILE_A_APP_ID);
|
||||
if (ret) {
|
||||
ESP_LOGE(TAG, "GATTC app register failed, error = %x", ret);
|
||||
return;
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "BLE Power Control Central example started");
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
# This file was generated using idf.py save-defconfig. It can be edited manually.
|
||||
# Espressif IoT Development Framework (ESP-IDF) Project Minimal Configuration
|
||||
#
|
||||
# BT config - Enable Bluetooth
|
||||
CONFIG_BT_ENABLED=y
|
||||
|
||||
# Enable Bluedroid host
|
||||
CONFIG_BT_BLUEDROID_ENABLED=y
|
||||
|
||||
# Enable BLE 5.0 features (required for Power Control)
|
||||
CONFIG_BT_BLE_50_FEATURES_SUPPORTED=y
|
||||
|
||||
# Enable BLE Extended Scan (required for BLE 5.0 scanning)
|
||||
CONFIG_BT_BLE_50_EXTEND_SCAN_EN=y
|
||||
|
||||
# Enable BLE Power Control feature (Host)
|
||||
CONFIG_BT_BLE_FEAT_POWER_CONTROL=y
|
||||
|
||||
# Enable BLE Power Control feature (Controller)
|
||||
CONFIG_BT_LE_POWER_CONTROL_ENABLED=y
|
||||
|
||||
|
||||
CONFIG_BT_BLE_SMP_ENABLE=n
|
||||
|
||||
CONFIG_BT_GATTS_ENABLE=n
|
||||
|
||||
CONFIG_BT_BLE_50_EXTEND_ADV_EN=n
|
||||
|
||||
CONFIG_BT_BLE_50_EXTEND_SYNC_EN=n
|
||||
|
||||
CONFIG_BT_BLE_50_DTM_TEST_EN=n
|
||||
@@ -0,0 +1,8 @@
|
||||
# The following lines of boilerplate have to be in your project's CMakeLists
|
||||
# in this exact order for cmake to work correctly
|
||||
cmake_minimum_required(VERSION 3.22)
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
# "Trim" the build. Include the minimal set of components, main, and anything it depends on.
|
||||
idf_build_set_property(MINIMAL_BUILD ON)
|
||||
project(ble_power_control_peripheral)
|
||||
@@ -0,0 +1,48 @@
|
||||
| Supported Targets | ESP32-C5 | ESP32-C6 | ESP32-C61 | ESP32-H2 |
|
||||
| ----------------- | -------- | -------- | --------- | -------- |
|
||||
|
||||
# BLE Power Control Peripheral Example
|
||||
|
||||
This example demonstrates how to use BLE Power Control feature as a Peripheral device using Bluedroid host API.
|
||||
|
||||
## What is BLE Power Control?
|
||||
|
||||
BLE Power Control is a BLE 5.0 feature that allows devices to monitor and control transmit power levels, and report path loss between connected devices. This helps optimize power consumption and maintain connection quality.
|
||||
|
||||
## How It Works
|
||||
|
||||
1. The Peripheral device starts advertising
|
||||
2. When a Central connects, the Peripheral accepts the connection
|
||||
3. After connection, the Peripheral reads remote transmit power level and enables power reporting
|
||||
4. The Peripheral receives power events when conditions change
|
||||
|
||||
## API Usage
|
||||
|
||||
- `esp_ble_gap_read_remote_transmit_power_level()` - Read remote transmit power level
|
||||
- `esp_ble_gap_set_transmit_power_reporting_enable()` - Enable transmit power reporting
|
||||
- `ESP_GAP_BLE_TRANS_PWR_RPTING_EVT` - Event received when transmit power changes
|
||||
|
||||
## Configuration
|
||||
|
||||
The example requires BLE 5.0 features to be enabled:
|
||||
- `CONFIG_BT_BLE_50_FEATURES_SUPPORTED=y`
|
||||
- `CONFIG_BT_BLE_FEAT_POWER_CONTROL=y`
|
||||
|
||||
## Hardware Required
|
||||
|
||||
- One ESP32 development board (ESP32-C5, ESP32-C6, ESP32-H2, etc.) that supports BLE 5.0
|
||||
|
||||
## Build and Flash
|
||||
|
||||
```bash
|
||||
idf.py set-target esp32c5
|
||||
idf.py build flash monitor
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
1. Flash this example to one ESP32 device (Peripheral)
|
||||
2. Flash the `ble_power_control_central` example to another ESP32 device (Central)
|
||||
3. The Peripheral will start advertising and wait for connection
|
||||
4. Monitor the serial output to see power events
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
idf_component_register(SRCS "main.c"
|
||||
PRIV_REQUIRES bt nvs_flash
|
||||
INCLUDE_DIRS ".")
|
||||
@@ -0,0 +1,428 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Unlicense OR CC0-1.0
|
||||
*/
|
||||
|
||||
/****************************************************************************
|
||||
*
|
||||
* This demo showcases BLE Power Control feature on Peripheral side.
|
||||
* It advertises and waits for connection from a central device. After connection,
|
||||
* it reads remote transmit power level and enables power reporting to monitor
|
||||
* power consumption.
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "freertos/semphr.h"
|
||||
#include "esp_system.h"
|
||||
#include "esp_log.h"
|
||||
#include "nvs_flash.h"
|
||||
#include "esp_bt.h"
|
||||
#include "esp_gap_ble_api.h"
|
||||
#include "esp_gatts_api.h"
|
||||
#include "esp_bt_defs.h"
|
||||
#include "esp_bt_main.h"
|
||||
#include "esp_gatt_common_api.h"
|
||||
#include "power_control_service.h"
|
||||
|
||||
#define TAG "BLE_POWER_CONTROL_PERIPHERAL"
|
||||
#define DEVICE_NAME "ESP_PWR_CTL_PRH"
|
||||
|
||||
#define PROFILE_NUM 1
|
||||
#define PROFILE_A_APP_ID 0
|
||||
#define SVC_INST_ID 0
|
||||
|
||||
#define EXT_ADV_HANDLE 0
|
||||
#define NUM_EXT_ADV 1
|
||||
|
||||
#define SEM_WAIT_TIMEOUT_MS 5000
|
||||
|
||||
#define FUNC_SEND_WAIT_SEM(func, sem) do {\
|
||||
esp_err_t __err_rc = (func);\
|
||||
if (__err_rc != ESP_OK) { \
|
||||
ESP_LOGE(TAG, "%s, message send fail, error = %d", __func__, __err_rc); \
|
||||
} \
|
||||
if (xSemaphoreTake(sem, pdMS_TO_TICKS(SEM_WAIT_TIMEOUT_MS)) != pdTRUE) { \
|
||||
ESP_LOGE(TAG, "%s, semaphore timeout", __func__); \
|
||||
} \
|
||||
} while(0);
|
||||
|
||||
static SemaphoreHandle_t test_sem = NULL;
|
||||
static uint16_t conn_handle = 0xFFFF;
|
||||
|
||||
uint16_t power_control_handle_table[POWER_CONTROL_IDX_NB];
|
||||
|
||||
/* UUIDs */
|
||||
static const uint16_t primary_service_uuid = ESP_GATT_UUID_PRI_SERVICE;
|
||||
static const uint16_t character_declaration_uuid = ESP_GATT_UUID_CHAR_DECLARE;
|
||||
|
||||
/* Service UUID - must be a variable, not a macro, to take address */
|
||||
static const uint16_t power_control_service_uuid = BLE_UUID_POWER_CONTROL_SERVICE_VAL;
|
||||
static const uint16_t power_level_char_uuid = BLE_UUID_POWER_CONTROL_SERVICE_VAL;
|
||||
|
||||
/* Characteristic properties */
|
||||
static const uint8_t char_prop_read = ESP_GATT_CHAR_PROP_BIT_READ;
|
||||
|
||||
/* Power Level value */
|
||||
static uint8_t power_level_value[1] = {0x00};
|
||||
|
||||
/* Full Database Description - Used to add attributes into the database */
|
||||
static const esp_gatts_attr_db_t gatt_db[POWER_CONTROL_IDX_NB] = {
|
||||
// Service Declaration
|
||||
[IDX_POWER_CONTROL_SVC] =
|
||||
{{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&primary_service_uuid, ESP_GATT_PERM_READ,
|
||||
sizeof(uint16_t), sizeof(power_control_service_uuid), (uint8_t *)&power_control_service_uuid}},
|
||||
|
||||
/* Power Level Characteristic Declaration */
|
||||
[IDX_POWER_LEVEL_CHAR] =
|
||||
{{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&character_declaration_uuid, ESP_GATT_PERM_READ,
|
||||
sizeof(uint8_t), sizeof(uint8_t), (uint8_t *)&char_prop_read}},
|
||||
|
||||
/* Power Level Characteristic Value */
|
||||
[IDX_POWER_LEVEL_VAL] =
|
||||
{{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&power_level_char_uuid, ESP_GATT_PERM_READ,
|
||||
sizeof(power_level_value), sizeof(power_level_value), (uint8_t *)power_level_value}},
|
||||
};
|
||||
|
||||
// Extended advertising data (includes device name)
|
||||
// Format: [length][AD type][data...]
|
||||
// Flag: 0x02 (length) + 0x01 (AD type) + 0x06 (flag value) = 3 bytes
|
||||
// Name: 0x10 (length) + 0x09 (AD type) + 15 bytes (name) = 17 bytes
|
||||
static uint8_t raw_ext_adv_data[] = {
|
||||
0x02, ESP_BLE_AD_TYPE_FLAG, 0x06,
|
||||
0x10, ESP_BLE_AD_TYPE_NAME_CMPL, 'E','S','P','_','P','W','R','_','C','T','L','_','P','R','H'
|
||||
};
|
||||
|
||||
static esp_ble_gap_ext_adv_params_t ext_adv_params = {
|
||||
.type = ESP_BLE_GAP_SET_EXT_ADV_PROP_CONNECTABLE,
|
||||
.interval_min = 0x20, // 32 * 0.625ms = 20ms
|
||||
.interval_max = 0x40, // 64 * 0.625ms = 40ms
|
||||
.channel_map = ADV_CHNL_ALL,
|
||||
.filter_policy = ADV_FILTER_ALLOW_SCAN_ANY_CON_ANY,
|
||||
.primary_phy = ESP_BLE_GAP_PHY_1M,
|
||||
.max_skip = 0,
|
||||
.secondary_phy = ESP_BLE_GAP_PHY_1M,
|
||||
.sid = 0,
|
||||
.scan_req_notif = false,
|
||||
.own_addr_type = BLE_ADDR_TYPE_PUBLIC,
|
||||
.tx_power = EXT_ADV_TX_PWR_NO_PREFERENCE,
|
||||
};
|
||||
|
||||
static esp_ble_gap_ext_adv_t ext_adv[1] = {
|
||||
[0] = {EXT_ADV_HANDLE, 0, 0},
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Initialize power control features after connection
|
||||
*/
|
||||
static void init_power_control(uint16_t conn_handle)
|
||||
{
|
||||
if (conn_handle == 0xFFFF) {
|
||||
ESP_LOGE(TAG, "Invalid connection handle");
|
||||
return;
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "Initializing power control features...");
|
||||
|
||||
// Read remote transmit power level (LE 1M PHY)
|
||||
esp_err_t ret = esp_ble_gap_read_remote_transmit_power_level(conn_handle, ESP_BLE_CONN_TX_POWER_PHY_1M);
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to read remote transmit power level, error = 0x%x", ret);
|
||||
} else {
|
||||
ESP_LOGI(TAG, "Read remote transmit power level request sent");
|
||||
}
|
||||
|
||||
// Enable transmit power reporting (both local and remote)
|
||||
ret = esp_ble_gap_set_transmit_power_reporting_enable(conn_handle, true, true);
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to enable transmit power reporting, error = 0x%x", ret);
|
||||
} else {
|
||||
ESP_LOGI(TAG, "Transmit power reporting enabled");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief GAP event handler
|
||||
*/
|
||||
static void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param)
|
||||
{
|
||||
if (param == NULL) {
|
||||
ESP_LOGE(TAG, "GAP event handler received NULL param");
|
||||
return;
|
||||
}
|
||||
|
||||
switch (event) {
|
||||
case ESP_GAP_BLE_EXT_ADV_SET_PARAMS_COMPLETE_EVT:
|
||||
xSemaphoreGive(test_sem);
|
||||
ESP_LOGI(TAG, "Extended advertising params set, status %d, instance %d",
|
||||
param->ext_adv_set_params.status, param->ext_adv_set_params.instance);
|
||||
break;
|
||||
case ESP_GAP_BLE_EXT_ADV_DATA_SET_COMPLETE_EVT:
|
||||
xSemaphoreGive(test_sem);
|
||||
ESP_LOGI(TAG, "Extended advertising data set, status %d, instance %d",
|
||||
param->ext_adv_data_set.status, param->ext_adv_data_set.instance);
|
||||
break;
|
||||
case ESP_GAP_BLE_EXT_ADV_START_COMPLETE_EVT:
|
||||
xSemaphoreGive(test_sem);
|
||||
if (param->ext_adv_start.status != ESP_BT_STATUS_SUCCESS) {
|
||||
ESP_LOGE(TAG, "Extended advertising start failed, status %d", param->ext_adv_start.status);
|
||||
break;
|
||||
}
|
||||
ESP_LOGI(TAG, "Extended advertising start successfully, instance num %d",
|
||||
param->ext_adv_start.instance_num);
|
||||
break;
|
||||
case ESP_GAP_BLE_EXT_ADV_STOP_COMPLETE_EVT:
|
||||
if (param->ext_adv_stop.status != ESP_BT_STATUS_SUCCESS) {
|
||||
ESP_LOGE(TAG, "Extended advertising stop failed, status %d", param->ext_adv_stop.status);
|
||||
break;
|
||||
}
|
||||
ESP_LOGI(TAG, "Extended advertising stop successfully");
|
||||
break;
|
||||
case ESP_GAP_BLE_UPDATE_CONN_PARAMS_EVT:
|
||||
ESP_LOGI(TAG, "Connection parameters updated: min_int=%d, max_int=%d, conn_int=%d, latency=%d, timeout=%d",
|
||||
param->update_conn_params.min_int,
|
||||
param->update_conn_params.max_int,
|
||||
param->update_conn_params.conn_int,
|
||||
param->update_conn_params.latency,
|
||||
param->update_conn_params.timeout);
|
||||
break;
|
||||
case ESP_GAP_BLE_READ_REMOTE_TRANS_PWR_LEVEL_EVT:
|
||||
ESP_LOGI(TAG, "Read remote transmit power level complete, status %d", param->read_remote_trans_pwr_level_cmpl.status);
|
||||
break;
|
||||
case ESP_GAP_BLE_SET_TRANS_PWR_RPTING_ENABLE_EVT:
|
||||
ESP_LOGI(TAG, "Set transmit power reporting enable complete, status %d, conn_handle=%d",
|
||||
param->set_trans_pwr_rpting_enable.status,
|
||||
param->set_trans_pwr_rpting_enable.conn_handle);
|
||||
break;
|
||||
case ESP_GAP_BLE_TRANS_PWR_RPTING_EVT:
|
||||
ESP_LOGI(TAG, "Transmit power reporting event: conn_handle=%d, status=%d, reason=%d, phy=%d, power_level=%d, power_level_flag=%d, delta=%d",
|
||||
param->trans_power_report_evt.conn_handle,
|
||||
param->trans_power_report_evt.status,
|
||||
param->trans_power_report_evt.reason,
|
||||
param->trans_power_report_evt.phy,
|
||||
param->trans_power_report_evt.tx_power_level,
|
||||
param->trans_power_report_evt.tx_power_level_flag,
|
||||
param->trans_power_report_evt.delta);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
struct gatts_profile_inst {
|
||||
esp_gatts_cb_t gatts_cb;
|
||||
uint16_t gatts_if;
|
||||
uint16_t app_id;
|
||||
uint16_t conn_id;
|
||||
};
|
||||
|
||||
static void gatts_profile_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param);
|
||||
|
||||
static struct gatts_profile_inst gl_profile_tab[PROFILE_NUM] = {
|
||||
[PROFILE_A_APP_ID] = {
|
||||
.gatts_cb = gatts_profile_event_handler,
|
||||
.gatts_if = ESP_GATT_IF_NONE,
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief GATTS profile event handler
|
||||
*/
|
||||
static void gatts_profile_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param)
|
||||
{
|
||||
if (param == NULL) {
|
||||
ESP_LOGE(TAG, "GATTS event handler received NULL param");
|
||||
return;
|
||||
}
|
||||
|
||||
switch (event) {
|
||||
case ESP_GATTS_REG_EVT: {
|
||||
ESP_LOGI(TAG, "GATT server register, status %d, app_id %d, gatts_if %d",
|
||||
param->reg.status, param->reg.app_id, gatts_if);
|
||||
|
||||
esp_ble_gap_set_device_name(DEVICE_NAME);
|
||||
|
||||
// Create attribute table
|
||||
esp_err_t create_attr_ret = esp_ble_gatts_create_attr_tab(gatt_db, gatts_if, POWER_CONTROL_IDX_NB, SVC_INST_ID);
|
||||
if (create_attr_ret) {
|
||||
ESP_LOGE(TAG, "create attr table failed, error code = %x", create_attr_ret);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ESP_GATTS_CREAT_ATTR_TAB_EVT: {
|
||||
if (param->add_attr_tab.status != ESP_GATT_OK) {
|
||||
ESP_LOGE(TAG, "create attribute table failed, error code=0x%x", param->add_attr_tab.status);
|
||||
} else if (param->add_attr_tab.num_handle != POWER_CONTROL_IDX_NB) {
|
||||
ESP_LOGE(TAG, "create attribute table abnormally, num_handle (%d) doesn't equal to POWER_CONTROL_IDX_NB(%d)",
|
||||
param->add_attr_tab.num_handle, POWER_CONTROL_IDX_NB);
|
||||
} else {
|
||||
ESP_LOGI(TAG, "create attribute table successfully, the number handle = %d", param->add_attr_tab.num_handle);
|
||||
// Validate handles pointer before memcpy
|
||||
if (param->add_attr_tab.handles) {
|
||||
memcpy(power_control_handle_table, param->add_attr_tab.handles, sizeof(power_control_handle_table));
|
||||
esp_ble_gatts_start_service(power_control_handle_table[IDX_POWER_CONTROL_SVC]);
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Invalid handles pointer");
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ESP_GATTS_START_EVT:
|
||||
ESP_LOGI(TAG, "Service start, status %d, service_handle %d",
|
||||
param->start.status, param->start.service_handle);
|
||||
break;
|
||||
case ESP_GATTS_CONNECT_EVT: {
|
||||
ESP_LOGI(TAG, "Connected, conn_id %u, remote "ESP_BD_ADDR_STR"",
|
||||
param->connect.conn_id, ESP_BD_ADDR_HEX(param->connect.remote_bda));
|
||||
gl_profile_tab[PROFILE_A_APP_ID].conn_id = param->connect.conn_id;
|
||||
conn_handle = param->connect.conn_id;
|
||||
|
||||
esp_ble_conn_update_params_t conn_params = {0};
|
||||
memcpy(conn_params.bda, param->connect.remote_bda, sizeof(esp_bd_addr_t));
|
||||
conn_params.latency = 0;
|
||||
conn_params.max_int = 0x20;
|
||||
conn_params.min_int = 0x10;
|
||||
conn_params.timeout = 400;
|
||||
esp_ble_gap_update_conn_params(&conn_params);
|
||||
|
||||
// Stop extended advertising
|
||||
uint8_t adv_handle = EXT_ADV_HANDLE;
|
||||
esp_ble_gap_ext_adv_stop(1, &adv_handle);
|
||||
// Initialize power control after connection
|
||||
init_power_control(conn_handle);
|
||||
break;
|
||||
}
|
||||
case ESP_GATTS_DISCONNECT_EVT:
|
||||
ESP_LOGI(TAG, "Disconnected, remote "ESP_BD_ADDR_STR", reason 0x%02x",
|
||||
ESP_BD_ADDR_HEX(param->disconnect.remote_bda), param->disconnect.reason);
|
||||
conn_handle = 0xFFFF;
|
||||
// Restart extended advertising
|
||||
FUNC_SEND_WAIT_SEM(esp_ble_gap_ext_adv_start(NUM_EXT_ADV, ext_adv), test_sem);
|
||||
break;
|
||||
case ESP_GATTS_READ_EVT:
|
||||
ESP_LOGI(TAG, "Read event, handle %d", param->read.handle);
|
||||
// Auto response is enabled, so no need to send response manually
|
||||
break;
|
||||
case ESP_GATTS_WRITE_EVT:
|
||||
ESP_LOGI(TAG, "Write event, handle %d, len %d", param->write.handle, param->write.len);
|
||||
// Auto response is enabled, so no need to send response manually
|
||||
break;
|
||||
case ESP_GATTS_MTU_EVT:
|
||||
ESP_LOGI(TAG, "MTU exchange, MTU %d", param->mtu.mtu);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void esp_gatts_cb(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param)
|
||||
{
|
||||
if (event == ESP_GATTS_REG_EVT) {
|
||||
if (param->reg.status == ESP_GATT_OK) {
|
||||
gl_profile_tab[param->reg.app_id].gatts_if = gatts_if;
|
||||
} else {
|
||||
ESP_LOGI(TAG, "reg app failed, app_id %04x, status %d",
|
||||
param->reg.app_id, param->reg.status);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
do {
|
||||
int idx;
|
||||
for (idx = 0; idx < PROFILE_NUM; idx++) {
|
||||
if (gatts_if == ESP_GATT_IF_NONE ||
|
||||
gatts_if == gl_profile_tab[idx].gatts_if) {
|
||||
if (gl_profile_tab[idx].gatts_cb) {
|
||||
gl_profile_tab[idx].gatts_cb(event, gatts_if, param);
|
||||
}
|
||||
}
|
||||
}
|
||||
} while (0);
|
||||
}
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
esp_err_t ret;
|
||||
|
||||
// Initialize NVS
|
||||
ret = nvs_flash_init();
|
||||
if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
|
||||
ESP_ERROR_CHECK(nvs_flash_erase());
|
||||
ret = nvs_flash_init();
|
||||
}
|
||||
ESP_ERROR_CHECK(ret);
|
||||
|
||||
// Initialize Bluetooth Controller
|
||||
ESP_ERROR_CHECK(esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT));
|
||||
esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT();
|
||||
ret = esp_bt_controller_init(&bt_cfg);
|
||||
if (ret) {
|
||||
ESP_LOGE(TAG, "Bluetooth controller init failed, error = %x", ret);
|
||||
return;
|
||||
}
|
||||
|
||||
ret = esp_bt_controller_enable(ESP_BT_MODE_BLE);
|
||||
if (ret) {
|
||||
ESP_LOGE(TAG, "Bluetooth controller enable failed, error = %x", ret);
|
||||
return;
|
||||
}
|
||||
|
||||
// Initialize Bluedroid
|
||||
esp_bluedroid_config_t cfg = BT_BLUEDROID_INIT_CONFIG_DEFAULT();
|
||||
ret = esp_bluedroid_init_with_cfg(&cfg);
|
||||
if (ret) {
|
||||
ESP_LOGE(TAG, "Bluedroid init failed, error = %x", ret);
|
||||
return;
|
||||
}
|
||||
|
||||
ret = esp_bluedroid_enable();
|
||||
if (ret) {
|
||||
ESP_LOGE(TAG, "Bluedroid enable failed, error = %x", ret);
|
||||
return;
|
||||
}
|
||||
|
||||
// Register callbacks
|
||||
ret = esp_ble_gap_register_callback(gap_event_handler);
|
||||
if (ret) {
|
||||
ESP_LOGE(TAG, "GAP register callback failed, error = %x", ret);
|
||||
return;
|
||||
}
|
||||
|
||||
ret = esp_ble_gatts_register_callback(esp_gatts_cb);
|
||||
if (ret) {
|
||||
ESP_LOGE(TAG, "gatts register error, error code = %x", ret);
|
||||
return;
|
||||
}
|
||||
|
||||
ret = esp_ble_gatts_app_register(PROFILE_A_APP_ID);
|
||||
if (ret) {
|
||||
ESP_LOGE(TAG, "gatts app register error, error code = %x", ret);
|
||||
return;
|
||||
}
|
||||
|
||||
// Create semaphore for extended advertising
|
||||
test_sem = xSemaphoreCreateBinary();
|
||||
|
||||
// Start extended advertising
|
||||
esp_bd_addr_t addr;
|
||||
esp_ble_gap_addr_create_static(addr);
|
||||
ESP_LOGI(TAG, "Device Address: "ESP_BD_ADDR_STR"", ESP_BD_ADDR_HEX(addr));
|
||||
|
||||
// Set extended advertising parameters
|
||||
FUNC_SEND_WAIT_SEM(esp_ble_gap_ext_adv_set_params(EXT_ADV_HANDLE, &ext_adv_params), test_sem);
|
||||
|
||||
// Set extended advertising data
|
||||
FUNC_SEND_WAIT_SEM(esp_ble_gap_config_ext_adv_data_raw(EXT_ADV_HANDLE,
|
||||
sizeof(raw_ext_adv_data),
|
||||
raw_ext_adv_data), test_sem);
|
||||
|
||||
// Start extended advertising
|
||||
FUNC_SEND_WAIT_SEM(esp_ble_gap_ext_adv_start(NUM_EXT_ADV, ext_adv), test_sem);
|
||||
|
||||
ESP_LOGI(TAG, "BLE Power Control Peripheral example started");
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
|
||||
* SPDX-License-Identifier: Unlicense OR CC0-1.0
|
||||
*/
|
||||
|
||||
#ifndef POWER_CONTROL_SERVICE_H
|
||||
#define POWER_CONTROL_SERVICE_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
/* Power Control Service UUIDs */
|
||||
#define BLE_UUID_POWER_CONTROL_SERVICE_VAL 0xFF01
|
||||
|
||||
/* Attributes State Machine */
|
||||
enum {
|
||||
IDX_POWER_CONTROL_SVC,
|
||||
|
||||
/* Power Level Characteristic */
|
||||
IDX_POWER_LEVEL_CHAR,
|
||||
IDX_POWER_LEVEL_VAL,
|
||||
|
||||
POWER_CONTROL_IDX_NB,
|
||||
};
|
||||
|
||||
#endif /* POWER_CONTROL_SERVICE_H */
|
||||
@@ -0,0 +1,32 @@
|
||||
# This file was generated using idf.py save-defconfig. It can be edited manually.
|
||||
# Espressif IoT Development Framework (ESP-IDF) Project Minimal Configuration
|
||||
#
|
||||
# BT config - Enable Bluetooth
|
||||
CONFIG_BT_ENABLED=y
|
||||
|
||||
# Enable Bluedroid host
|
||||
CONFIG_BT_BLUEDROID_ENABLED=y
|
||||
|
||||
# Enable BLE 5.0 features (required for Power Control)
|
||||
CONFIG_BT_BLE_50_FEATURES_SUPPORTED=y
|
||||
|
||||
# Enable BLE Extended Advertising (required for BLE 5.0 advertising)
|
||||
CONFIG_BT_BLE_50_EXTEND_ADV_EN=y
|
||||
|
||||
# Enable BLE Power Control feature (Host)
|
||||
CONFIG_BT_BLE_FEAT_POWER_CONTROL=y
|
||||
|
||||
# Enable BLE Power Control feature (Controller)
|
||||
CONFIG_BT_LE_POWER_CONTROL_ENABLED=y
|
||||
|
||||
|
||||
|
||||
CONFIG_BT_BLE_SMP_ENABLE=n
|
||||
|
||||
CONFIG_BT_GATTC_ENABLE=n
|
||||
|
||||
CONFIG_BT_BLE_50_EXTEND_SCAN_EN=y
|
||||
|
||||
CONFIG_BT_BLE_50_EXTEND_SYNC_EN=n
|
||||
|
||||
CONFIG_BT_BLE_50_DTM_TEST_EN=n
|
||||
Reference in New Issue
Block a user