diff --git a/components/bt/controller/esp32c5/Kconfig.in b/components/bt/controller/esp32c5/Kconfig.in index dbd2a8e212..8e57445217 100644 --- a/components/bt/controller/esp32c5/Kconfig.in +++ b/components/bt/controller/esp32c5/Kconfig.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 diff --git a/components/bt/controller/esp32c5/esp_bt_cfg.h b/components/bt/controller/esp32c5/esp_bt_cfg.h index 20f2ec7edb..b88b714697 100644 --- a/components/bt/controller/esp32c5/esp_bt_cfg.h +++ b/components/bt/controller/esp32c5/esp_bt_cfg.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2015-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2015-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -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 diff --git a/components/bt/controller/esp32c6/Kconfig.in b/components/bt/controller/esp32c6/Kconfig.in index 58fc31715d..a2fb88293e 100644 --- a/components/bt/controller/esp32c6/Kconfig.in +++ b/components/bt/controller/esp32c6/Kconfig.in @@ -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 diff --git a/components/bt/controller/esp32c6/esp_bt_cfg.h b/components/bt/controller/esp32c6/esp_bt_cfg.h index e5d378ad9b..057bbd6874 100644 --- a/components/bt/controller/esp32c6/esp_bt_cfg.h +++ b/components/bt/controller/esp32c6/esp_bt_cfg.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2015-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2015-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -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 diff --git a/components/bt/controller/esp32h2/Kconfig.in b/components/bt/controller/esp32h2/Kconfig.in index 47820f717c..b613c6f27d 100644 --- a/components/bt/controller/esp32h2/Kconfig.in +++ b/components/bt/controller/esp32h2/Kconfig.in @@ -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) diff --git a/components/bt/controller/esp32h2/esp_bt_cfg.h b/components/bt/controller/esp32h2/esp_bt_cfg.h index 20f2ec7edb..b88b714697 100644 --- a/components/bt/controller/esp32h2/esp_bt_cfg.h +++ b/components/bt/controller/esp32h2/esp_bt_cfg.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2015-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2015-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -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 diff --git a/components/bt/host/bluedroid/Kconfig.in b/components/bt/host/bluedroid/Kconfig.in index 9dfef03026..f82a9a7ef7 100644 --- a/components/bt/host/bluedroid/Kconfig.in +++ b/components/bt/host/bluedroid/Kconfig.in @@ -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 diff --git a/components/bt/host/bluedroid/api/include/api/esp_gattc_api.h b/components/bt/host/bluedroid/api/include/api/esp_gattc_api.h index 0d9837a5ca..1f00c59971 100644 --- a/components/bt/host/bluedroid/api/include/api/esp_gattc_api.h +++ b/components/bt/host/bluedroid/api/include/api/esp_gattc_api.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2015-2024 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2015-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -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 * diff --git a/components/bt/host/bluedroid/stack/btm/btm_ble_5_gap.c b/components/bt/host/bluedroid/stack/btm/btm_ble_5_gap.c index 7ecde99cee..46f0eadbeb 100644 --- a/components/bt/host/bluedroid/stack/btm/btm_ble_5_gap.c +++ b/components/bt/host/bluedroid/stack/btm/btm_ble_5_gap.c @@ -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; } diff --git a/components/bt/host/bluedroid/stack/gatt/gatt_main.c b/components/bt/host/bluedroid/stack/gatt/gatt_main.c index f92a2c392b..8c6d9e6641 100644 --- a/components/bt/host/bluedroid/stack/gatt/gatt_main.c +++ b/components/bt/host/bluedroid/stack/gatt/gatt_main.c @@ -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); diff --git a/components/bt/host/bluedroid/stack/include/stack/btm_ble_api.h b/components/bt/host/bluedroid/stack/include/stack/btm_ble_api.h index 33522b7aef..7c5e24945d 100644 --- a/components/bt/host/bluedroid/stack/include/stack/btm_ble_api.h +++ b/components/bt/host/bluedroid/stack/include/stack/btm_ble_api.h @@ -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; diff --git a/components/bt/host/bluedroid/stack/include/stack/hcimsgs.h b/components/bt/host/bluedroid/stack/include/stack/hcimsgs.h index c4da454d0f..2e003353da 100644 --- a/components/bt/host/bluedroid/stack/include/stack/hcimsgs.h +++ b/components/bt/host/bluedroid/stack/include/stack/hcimsgs.h @@ -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); diff --git a/components/bt/include/esp32c5/include/esp_bt.h b/components/bt/include/esp32c5/include/esp_bt.h index 478c4df11a..dda21599d3 100644 --- a/components/bt/include/esp32c5/include/esp_bt.h +++ b/components/bt/include/esp32c5/include/esp_bt.h @@ -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, \ diff --git a/components/bt/include/esp32c6/include/esp_bt.h b/components/bt/include/esp32c6/include/esp_bt.h index 5771335a99..9a171162dc 100644 --- a/components/bt/include/esp32c6/include/esp_bt.h +++ b/components/bt/include/esp32c6/include/esp_bt.h @@ -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, \ diff --git a/components/bt/include/esp32h2/include/esp_bt.h b/components/bt/include/esp32h2/include/esp_bt.h index f3d06ac7c5..28783c40ec 100644 --- a/components/bt/include/esp32h2/include/esp_bt.h +++ b/components/bt/include/esp32h2/include/esp_bt.h @@ -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, \ diff --git a/components/soc/esp32c5/include/soc/Kconfig.soc_caps.in b/components/soc/esp32c5/include/soc/Kconfig.soc_caps.in index e3fda631de..dd0e3ce487 100644 --- a/components/soc/esp32c5/include/soc/Kconfig.soc_caps.in +++ b/components/soc/esp32c5/include/soc/Kconfig.soc_caps.in @@ -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 diff --git a/components/soc/esp32c5/include/soc/soc_caps.h b/components/soc/esp32c5/include/soc/soc_caps.h index 6da0a14551..74c6113ed6 100644 --- a/components/soc/esp32c5/include/soc/soc_caps.h +++ b/components/soc/esp32c5/include/soc/soc_caps.h @@ -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 -------------------------------------*/ diff --git a/components/soc/esp32c6/include/soc/Kconfig.soc_caps.in b/components/soc/esp32c6/include/soc/Kconfig.soc_caps.in index e601a03e95..503f1e7dd8 100644 --- a/components/soc/esp32c6/include/soc/Kconfig.soc_caps.in +++ b/components/soc/esp32c6/include/soc/Kconfig.soc_caps.in @@ -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 diff --git a/components/soc/esp32c6/include/soc/soc_caps.h b/components/soc/esp32c6/include/soc/soc_caps.h index 39d5cd917c..d2f98fbbb5 100644 --- a/components/soc/esp32c6/include/soc/soc_caps.h +++ b/components/soc/esp32c6/include/soc/soc_caps.h @@ -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) diff --git a/components/soc/esp32c61/include/soc/Kconfig.soc_caps.in b/components/soc/esp32c61/include/soc/Kconfig.soc_caps.in index d73d6a60f4..427cb914a4 100644 --- a/components/soc/esp32c61/include/soc/Kconfig.soc_caps.in +++ b/components/soc/esp32c61/include/soc/Kconfig.soc_caps.in @@ -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 diff --git a/components/soc/esp32c61/include/soc/soc_caps.h b/components/soc/esp32c61/include/soc/soc_caps.h index 6599966a5d..e5f76ac392 100644 --- a/components/soc/esp32c61/include/soc/soc_caps.h +++ b/components/soc/esp32c61/include/soc/soc_caps.h @@ -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 -------------------------------------*/ diff --git a/components/soc/esp32h2/include/soc/Kconfig.soc_caps.in b/components/soc/esp32h2/include/soc/Kconfig.soc_caps.in index 41d96b8b4d..df47af0026 100644 --- a/components/soc/esp32h2/include/soc/Kconfig.soc_caps.in +++ b/components/soc/esp32h2/include/soc/Kconfig.soc_caps.in @@ -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 diff --git a/components/soc/esp32h2/include/soc/soc_caps.h b/components/soc/esp32h2/include/soc/soc_caps.h index 3efb3865d5..bf632edeb5 100644 --- a/components/soc/esp32h2/include/soc/soc_caps.h +++ b/components/soc/esp32h2/include/soc/soc_caps.h @@ -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 -------------------------------------*/ diff --git a/examples/bluetooth/.build-test-rules.yml b/examples/bluetooth/.build-test-rules.yml index 98d02efbd8..35694c2aee 100644 --- a/examples/bluetooth/.build-test-rules.yml +++ b/examples/bluetooth/.build-test-rules.yml @@ -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: diff --git a/examples/bluetooth/bluedroid/ble_50/ble_conn_subrating_central/CMakeLists.txt b/examples/bluetooth/bluedroid/ble_50/ble_conn_subrating_central/CMakeLists.txt new file mode 100644 index 0000000000..0bd83d6816 --- /dev/null +++ b/examples/bluetooth/bluedroid/ble_50/ble_conn_subrating_central/CMakeLists.txt @@ -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) diff --git a/examples/bluetooth/bluedroid/ble_50/ble_conn_subrating_central/README.md b/examples/bluetooth/bluedroid/ble_50/ble_conn_subrating_central/README.md new file mode 100644 index 0000000000..8c09d5de74 --- /dev/null +++ b/examples/bluetooth/bluedroid/ble_50/ble_conn_subrating_central/README.md @@ -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 + diff --git a/examples/bluetooth/bluedroid/ble_50/ble_conn_subrating_central/main/CMakeLists.txt b/examples/bluetooth/bluedroid/ble_50/ble_conn_subrating_central/main/CMakeLists.txt new file mode 100644 index 0000000000..f677a03338 --- /dev/null +++ b/examples/bluetooth/bluedroid/ble_50/ble_conn_subrating_central/main/CMakeLists.txt @@ -0,0 +1,3 @@ +idf_component_register(SRCS "main.c" + PRIV_REQUIRES bt nvs_flash + INCLUDE_DIRS ".") diff --git a/examples/bluetooth/bluedroid/ble_50/ble_conn_subrating_central/main/main.c b/examples/bluetooth/bluedroid/ble_50/ble_conn_subrating_central/main/main.c new file mode 100644 index 0000000000..9b703a42bf --- /dev/null +++ b/examples/bluetooth/bluedroid/ble_50/ble_conn_subrating_central/main/main.c @@ -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 +#include +#include +#include +#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"); +} diff --git a/examples/bluetooth/bluedroid/ble_50/ble_conn_subrating_central/sdkconfig.defaults b/examples/bluetooth/bluedroid/ble_50/ble_conn_subrating_central/sdkconfig.defaults new file mode 100644 index 0000000000..4fcdd7ebcc --- /dev/null +++ b/examples/bluetooth/bluedroid/ble_50/ble_conn_subrating_central/sdkconfig.defaults @@ -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 diff --git a/examples/bluetooth/bluedroid/ble_50/ble_conn_subrating_peripheral/CMakeLists.txt b/examples/bluetooth/bluedroid/ble_50/ble_conn_subrating_peripheral/CMakeLists.txt new file mode 100644 index 0000000000..5513eadafc --- /dev/null +++ b/examples/bluetooth/bluedroid/ble_50/ble_conn_subrating_peripheral/CMakeLists.txt @@ -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) diff --git a/examples/bluetooth/bluedroid/ble_50/ble_conn_subrating_peripheral/README.md b/examples/bluetooth/bluedroid/ble_50/ble_conn_subrating_peripheral/README.md new file mode 100644 index 0000000000..7b1c23d53f --- /dev/null +++ b/examples/bluetooth/bluedroid/ble_50/ble_conn_subrating_peripheral/README.md @@ -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 + diff --git a/examples/bluetooth/bluedroid/ble_50/ble_conn_subrating_peripheral/main/CMakeLists.txt b/examples/bluetooth/bluedroid/ble_50/ble_conn_subrating_peripheral/main/CMakeLists.txt new file mode 100644 index 0000000000..f677a03338 --- /dev/null +++ b/examples/bluetooth/bluedroid/ble_50/ble_conn_subrating_peripheral/main/CMakeLists.txt @@ -0,0 +1,3 @@ +idf_component_register(SRCS "main.c" + PRIV_REQUIRES bt nvs_flash + INCLUDE_DIRS ".") diff --git a/examples/bluetooth/bluedroid/ble_50/ble_conn_subrating_peripheral/main/main.c b/examples/bluetooth/bluedroid/ble_50/ble_conn_subrating_peripheral/main/main.c new file mode 100644 index 0000000000..deb4952ab0 --- /dev/null +++ b/examples/bluetooth/bluedroid/ble_50/ble_conn_subrating_peripheral/main/main.c @@ -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 +#include +#include +#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"); +} diff --git a/examples/bluetooth/bluedroid/ble_50/ble_conn_subrating_peripheral/main/subrating_service.h b/examples/bluetooth/bluedroid/ble_50/ble_conn_subrating_peripheral/main/subrating_service.h new file mode 100644 index 0000000000..88c409f812 --- /dev/null +++ b/examples/bluetooth/bluedroid/ble_50/ble_conn_subrating_peripheral/main/subrating_service.h @@ -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 + +/* 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 */ diff --git a/examples/bluetooth/bluedroid/ble_50/ble_conn_subrating_peripheral/sdkconfig.defaults b/examples/bluetooth/bluedroid/ble_50/ble_conn_subrating_peripheral/sdkconfig.defaults new file mode 100644 index 0000000000..c806ef0519 --- /dev/null +++ b/examples/bluetooth/bluedroid/ble_50/ble_conn_subrating_peripheral/sdkconfig.defaults @@ -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 diff --git a/examples/bluetooth/bluedroid/ble_50/ble_connection_central_with_cte/CMakeLists.txt b/examples/bluetooth/bluedroid/ble_50/ble_connection_central_with_cte/CMakeLists.txt new file mode 100644 index 0000000000..bdfe36fe9d --- /dev/null +++ b/examples/bluetooth/bluedroid/ble_50/ble_connection_central_with_cte/CMakeLists.txt @@ -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) diff --git a/examples/bluetooth/bluedroid/ble_50/ble_connection_central_with_cte/README.md b/examples/bluetooth/bluedroid/ble_50/ble_connection_central_with_cte/README.md new file mode 100644 index 0000000000..75ed052729 --- /dev/null +++ b/examples/bluetooth/bluedroid/ble_50/ble_connection_central_with_cte/README.md @@ -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. diff --git a/examples/bluetooth/bluedroid/ble_50/ble_connection_central_with_cte/main/CMakeLists.txt b/examples/bluetooth/bluedroid/ble_50/ble_connection_central_with_cte/main/CMakeLists.txt new file mode 100644 index 0000000000..2291df7efe --- /dev/null +++ b/examples/bluetooth/bluedroid/ble_50/ble_connection_central_with_cte/main/CMakeLists.txt @@ -0,0 +1,3 @@ +idf_component_register(SRCS "connection_central_with_cte.c" + PRIV_REQUIRES bt nvs_flash + INCLUDE_DIRS ".") diff --git a/examples/bluetooth/bluedroid/ble_50/ble_connection_central_with_cte/main/Kconfig.projbuild b/examples/bluetooth/bluedroid/ble_50/ble_connection_central_with_cte/main/Kconfig.projbuild new file mode 100644 index 0000000000..e69de29bb2 diff --git a/examples/bluetooth/bluedroid/ble_50/ble_connection_central_with_cte/main/connection_central_with_cte.c b/examples/bluetooth/bluedroid/ble_50/ble_connection_central_with_cte/main/connection_central_with_cte.c new file mode 100644 index 0000000000..dd19418881 --- /dev/null +++ b/examples/bluetooth/bluedroid/ble_50/ble_connection_central_with_cte/main/connection_central_with_cte.c @@ -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 +#include +#include +#include +#include +#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)); + +} diff --git a/examples/bluetooth/bluedroid/ble_50/ble_connection_central_with_cte/sdkconfig.defaults b/examples/bluetooth/bluedroid/ble_50/ble_connection_central_with_cte/sdkconfig.defaults new file mode 100644 index 0000000000..6272796eef --- /dev/null +++ b/examples/bluetooth/bluedroid/ble_50/ble_connection_central_with_cte/sdkconfig.defaults @@ -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 diff --git a/examples/bluetooth/bluedroid/ble_50/ble_connection_peripheral_with_cte/CMakeLists.txt b/examples/bluetooth/bluedroid/ble_50/ble_connection_peripheral_with_cte/CMakeLists.txt new file mode 100644 index 0000000000..852120f156 --- /dev/null +++ b/examples/bluetooth/bluedroid/ble_50/ble_connection_peripheral_with_cte/CMakeLists.txt @@ -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) diff --git a/examples/bluetooth/bluedroid/ble_50/ble_connection_peripheral_with_cte/README.md b/examples/bluetooth/bluedroid/ble_50/ble_connection_peripheral_with_cte/README.md new file mode 100644 index 0000000000..12cca1d525 --- /dev/null +++ b/examples/bluetooth/bluedroid/ble_50/ble_connection_peripheral_with_cte/README.md @@ -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. diff --git a/examples/bluetooth/bluedroid/ble_50/ble_connection_peripheral_with_cte/main/CMakeLists.txt b/examples/bluetooth/bluedroid/ble_50/ble_connection_peripheral_with_cte/main/CMakeLists.txt new file mode 100644 index 0000000000..986884efa6 --- /dev/null +++ b/examples/bluetooth/bluedroid/ble_50/ble_connection_peripheral_with_cte/main/CMakeLists.txt @@ -0,0 +1,3 @@ +idf_component_register(SRCS "connection_peripheral_with_cte.c" + PRIV_REQUIRES bt nvs_flash + INCLUDE_DIRS ".") diff --git a/examples/bluetooth/bluedroid/ble_50/ble_connection_peripheral_with_cte/main/Kconfig.projbuild b/examples/bluetooth/bluedroid/ble_50/ble_connection_peripheral_with_cte/main/Kconfig.projbuild new file mode 100644 index 0000000000..e69de29bb2 diff --git a/examples/bluetooth/bluedroid/ble_50/ble_connection_peripheral_with_cte/main/connection_peripheral_with_cte.c b/examples/bluetooth/bluedroid/ble_50/ble_connection_peripheral_with_cte/main/connection_peripheral_with_cte.c new file mode 100644 index 0000000000..3711a1d907 --- /dev/null +++ b/examples/bluetooth/bluedroid/ble_50/ble_connection_peripheral_with_cte/main/connection_peripheral_with_cte.c @@ -0,0 +1,567 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ + +#include +#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)); +} diff --git a/examples/bluetooth/bluedroid/ble_50/ble_connection_peripheral_with_cte/main/connection_peripheral_with_cte.h b/examples/bluetooth/bluedroid/ble_50/ble_connection_peripheral_with_cte/main/connection_peripheral_with_cte.h new file mode 100644 index 0000000000..30722d0c92 --- /dev/null +++ b/examples/bluetooth/bluedroid/ble_50/ble_connection_peripheral_with_cte/main/connection_peripheral_with_cte.h @@ -0,0 +1,28 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ + + +#include +#include +#include + + +/* 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, +}; diff --git a/examples/bluetooth/bluedroid/ble_50/ble_connection_peripheral_with_cte/sdkconfig.defaults b/examples/bluetooth/bluedroid/ble_50/ble_connection_peripheral_with_cte/sdkconfig.defaults new file mode 100644 index 0000000000..3d045fb988 --- /dev/null +++ b/examples/bluetooth/bluedroid/ble_50/ble_connection_peripheral_with_cte/sdkconfig.defaults @@ -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 diff --git a/examples/bluetooth/bluedroid/ble_50/ble_pawr_advertiser/CMakeLists.txt b/examples/bluetooth/bluedroid/ble_50/ble_pawr_advertiser/CMakeLists.txt new file mode 100644 index 0000000000..54e66d22c0 --- /dev/null +++ b/examples/bluetooth/bluedroid/ble_50/ble_pawr_advertiser/CMakeLists.txt @@ -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}) diff --git a/examples/bluetooth/bluedroid/ble_50/ble_pawr_advertiser/README.md b/examples/bluetooth/bluedroid/ble_50/ble_pawr_advertiser/README.md new file mode 100644 index 0000000000..7822109fb3 --- /dev/null +++ b/examples/bluetooth/bluedroid/ble_50/ble_pawr_advertiser/README.md @@ -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 +``` + +### 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. + diff --git a/examples/bluetooth/bluedroid/ble_50/ble_pawr_advertiser/main/CMakeLists.txt b/examples/bluetooth/bluedroid/ble_50/ble_pawr_advertiser/main/CMakeLists.txt new file mode 100644 index 0000000000..f677a03338 --- /dev/null +++ b/examples/bluetooth/bluedroid/ble_50/ble_pawr_advertiser/main/CMakeLists.txt @@ -0,0 +1,3 @@ +idf_component_register(SRCS "main.c" + PRIV_REQUIRES bt nvs_flash + INCLUDE_DIRS ".") diff --git a/examples/bluetooth/bluedroid/ble_50/ble_pawr_advertiser/main/main.c b/examples/bluetooth/bluedroid/ble_50/ble_pawr_advertiser/main/main.c new file mode 100644 index 0000000000..871d871011 --- /dev/null +++ b/examples/bluetooth/bluedroid/ble_50/ble_pawr_advertiser/main/main.c @@ -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 +#include +#include +#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"); +} diff --git a/examples/bluetooth/bluedroid/ble_50/ble_pawr_advertiser/sdkconfig.defaults b/examples/bluetooth/bluedroid/ble_50/ble_pawr_advertiser/sdkconfig.defaults new file mode 100644 index 0000000000..fa70c13f46 --- /dev/null +++ b/examples/bluetooth/bluedroid/ble_50/ble_pawr_advertiser/sdkconfig.defaults @@ -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 diff --git a/examples/bluetooth/bluedroid/ble_50/ble_pawr_advertiser_conn/CMakeLists.txt b/examples/bluetooth/bluedroid/ble_50/ble_pawr_advertiser_conn/CMakeLists.txt new file mode 100644 index 0000000000..c6cdf0c6e3 --- /dev/null +++ b/examples/bluetooth/bluedroid/ble_50/ble_pawr_advertiser_conn/CMakeLists.txt @@ -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}) diff --git a/examples/bluetooth/bluedroid/ble_50/ble_pawr_advertiser_conn/README.md b/examples/bluetooth/bluedroid/ble_50/ble_pawr_advertiser_conn/README.md new file mode 100644 index 0000000000..626e236c36 --- /dev/null +++ b/examples/bluetooth/bluedroid/ble_50/ble_pawr_advertiser_conn/README.md @@ -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 + idf.py -p 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 + idf.py -p 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 +``` + +### 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. + diff --git a/examples/bluetooth/bluedroid/ble_50/ble_pawr_advertiser_conn/main/CMakeLists.txt b/examples/bluetooth/bluedroid/ble_50/ble_pawr_advertiser_conn/main/CMakeLists.txt new file mode 100644 index 0000000000..f677a03338 --- /dev/null +++ b/examples/bluetooth/bluedroid/ble_50/ble_pawr_advertiser_conn/main/CMakeLists.txt @@ -0,0 +1,3 @@ +idf_component_register(SRCS "main.c" + PRIV_REQUIRES bt nvs_flash + INCLUDE_DIRS ".") diff --git a/examples/bluetooth/bluedroid/ble_50/ble_pawr_advertiser_conn/main/main.c b/examples/bluetooth/bluedroid/ble_50/ble_pawr_advertiser_conn/main/main.c new file mode 100644 index 0000000000..6276a884b9 --- /dev/null +++ b/examples/bluetooth/bluedroid/ble_50/ble_pawr_advertiser_conn/main/main.c @@ -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 +#include +#include +#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"); +} diff --git a/examples/bluetooth/bluedroid/ble_50/ble_pawr_advertiser_conn/sdkconfig.defaults b/examples/bluetooth/bluedroid/ble_50/ble_pawr_advertiser_conn/sdkconfig.defaults new file mode 100644 index 0000000000..b0b55071e5 --- /dev/null +++ b/examples/bluetooth/bluedroid/ble_50/ble_pawr_advertiser_conn/sdkconfig.defaults @@ -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 diff --git a/examples/bluetooth/bluedroid/ble_50/ble_pawr_synchronizer/CMakeLists.txt b/examples/bluetooth/bluedroid/ble_50/ble_pawr_synchronizer/CMakeLists.txt new file mode 100644 index 0000000000..174c68be35 --- /dev/null +++ b/examples/bluetooth/bluedroid/ble_50/ble_pawr_synchronizer/CMakeLists.txt @@ -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) diff --git a/examples/bluetooth/bluedroid/ble_50/ble_pawr_synchronizer/README.md b/examples/bluetooth/bluedroid/ble_50/ble_pawr_synchronizer/README.md new file mode 100644 index 0000000000..7f5e05cc4e --- /dev/null +++ b/examples/bluetooth/bluedroid/ble_50/ble_pawr_synchronizer/README.md @@ -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 + idf.py -p 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 + idf.py -p 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` diff --git a/examples/bluetooth/bluedroid/ble_50/ble_pawr_synchronizer/main/CMakeLists.txt b/examples/bluetooth/bluedroid/ble_50/ble_pawr_synchronizer/main/CMakeLists.txt new file mode 100644 index 0000000000..d1272a2d77 --- /dev/null +++ b/examples/bluetooth/bluedroid/ble_50/ble_pawr_synchronizer/main/CMakeLists.txt @@ -0,0 +1,3 @@ +idf_component_register(SRCS "ble_pawr_synchronizer_demo.c" + PRIV_REQUIRES bt nvs_flash + INCLUDE_DIRS ".") diff --git a/examples/bluetooth/bluedroid/ble_50/ble_pawr_synchronizer/main/Kconfig.projbuild b/examples/bluetooth/bluedroid/ble_50/ble_pawr_synchronizer/main/Kconfig.projbuild new file mode 100644 index 0000000000..b0deae6b2f --- /dev/null +++ b/examples/bluetooth/bluedroid/ble_50/ble_pawr_synchronizer/main/Kconfig.projbuild @@ -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 diff --git a/examples/bluetooth/bluedroid/ble_50/ble_pawr_synchronizer/main/ble_pawr_synchronizer_demo.c b/examples/bluetooth/bluedroid/ble_50/ble_pawr_synchronizer/main/ble_pawr_synchronizer_demo.c new file mode 100644 index 0000000000..add4135109 --- /dev/null +++ b/examples/bluetooth/bluedroid/ble_50/ble_pawr_synchronizer/main/ble_pawr_synchronizer_demo.c @@ -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 +#include +#include +#include +#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; +} diff --git a/examples/bluetooth/bluedroid/ble_50/ble_pawr_synchronizer/sdkconfig.ci.esp32c2_xtal26m b/examples/bluetooth/bluedroid/ble_50/ble_pawr_synchronizer/sdkconfig.ci.esp32c2_xtal26m new file mode 100644 index 0000000000..463035ba5a --- /dev/null +++ b/examples/bluetooth/bluedroid/ble_50/ble_pawr_synchronizer/sdkconfig.ci.esp32c2_xtal26m @@ -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} diff --git a/examples/bluetooth/bluedroid/ble_50/ble_pawr_synchronizer/sdkconfig.ci.name b/examples/bluetooth/bluedroid/ble_50/ble_pawr_synchronizer/sdkconfig.ci.name new file mode 100644 index 0000000000..ce398ef858 --- /dev/null +++ b/examples/bluetooth/bluedroid/ble_50/ble_pawr_synchronizer/sdkconfig.ci.name @@ -0,0 +1,2 @@ +CONFIG_EXAMPLE_CI_ID=8 +CONFIG_EXAMPLE_CI_PIPELINE_ID=${CI_PIPELINE_ID} diff --git a/examples/bluetooth/bluedroid/ble_50/ble_pawr_synchronizer/sdkconfig.defaults b/examples/bluetooth/bluedroid/ble_50/ble_pawr_synchronizer/sdkconfig.defaults new file mode 100644 index 0000000000..7b444caaf1 --- /dev/null +++ b/examples/bluetooth/bluedroid/ble_50/ble_pawr_synchronizer/sdkconfig.defaults @@ -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 diff --git a/examples/bluetooth/bluedroid/ble_50/ble_pawr_synchronizer/sdkconfig.defaults.esp32c2 b/examples/bluetooth/bluedroid/ble_50/ble_pawr_synchronizer/sdkconfig.defaults.esp32c2 new file mode 100644 index 0000000000..d7addb1c67 --- /dev/null +++ b/examples/bluetooth/bluedroid/ble_50/ble_pawr_synchronizer/sdkconfig.defaults.esp32c2 @@ -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 diff --git a/examples/bluetooth/bluedroid/ble_50/ble_pawr_synchronizer/sdkconfig.defaults.esp32c3 b/examples/bluetooth/bluedroid/ble_50/ble_pawr_synchronizer/sdkconfig.defaults.esp32c3 new file mode 100644 index 0000000000..641ed53e33 --- /dev/null +++ b/examples/bluetooth/bluedroid/ble_50/ble_pawr_synchronizer/sdkconfig.defaults.esp32c3 @@ -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 diff --git a/examples/bluetooth/bluedroid/ble_50/ble_pawr_synchronizer/sdkconfig.defaults.esp32s3 b/examples/bluetooth/bluedroid/ble_50/ble_pawr_synchronizer/sdkconfig.defaults.esp32s3 new file mode 100644 index 0000000000..f15a09ef2a --- /dev/null +++ b/examples/bluetooth/bluedroid/ble_50/ble_pawr_synchronizer/sdkconfig.defaults.esp32s3 @@ -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 diff --git a/examples/bluetooth/bluedroid/ble_50/ble_periodic_adv_with_cte/CMakeLists.txt b/examples/bluetooth/bluedroid/ble_50/ble_periodic_adv_with_cte/CMakeLists.txt new file mode 100644 index 0000000000..1dd9bc8892 --- /dev/null +++ b/examples/bluetooth/bluedroid/ble_50/ble_periodic_adv_with_cte/CMakeLists.txt @@ -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) diff --git a/examples/bluetooth/bluedroid/ble_50/ble_periodic_adv_with_cte/README.md b/examples/bluetooth/bluedroid/ble_50/ble_periodic_adv_with_cte/README.md new file mode 100644 index 0000000000..42d37138e1 --- /dev/null +++ b/examples/bluetooth/bluedroid/ble_50/ble_periodic_adv_with_cte/README.md @@ -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. diff --git a/examples/bluetooth/bluedroid/ble_50/ble_periodic_adv_with_cte/main/CMakeLists.txt b/examples/bluetooth/bluedroid/ble_50/ble_periodic_adv_with_cte/main/CMakeLists.txt new file mode 100644 index 0000000000..583125ce80 --- /dev/null +++ b/examples/bluetooth/bluedroid/ble_50/ble_periodic_adv_with_cte/main/CMakeLists.txt @@ -0,0 +1,3 @@ +idf_component_register(SRCS "periodic_adv_with_cte_demo.c" + PRIV_REQUIRES bt nvs_flash + INCLUDE_DIRS ".") diff --git a/examples/bluetooth/bluedroid/ble_50/ble_periodic_adv_with_cte/main/periodic_adv_with_cte_demo.c b/examples/bluetooth/bluedroid/ble_50/ble_periodic_adv_with_cte/main/periodic_adv_with_cte_demo.c new file mode 100644 index 0000000000..f6d5cea052 --- /dev/null +++ b/examples/bluetooth/bluedroid/ble_50/ble_periodic_adv_with_cte/main/periodic_adv_with_cte_demo.c @@ -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 +#include +#include +#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; +} diff --git a/examples/bluetooth/bluedroid/ble_50/ble_periodic_adv_with_cte/sdkconfig.defaults b/examples/bluetooth/bluedroid/ble_50/ble_periodic_adv_with_cte/sdkconfig.defaults new file mode 100644 index 0000000000..fa787c9290 --- /dev/null +++ b/examples/bluetooth/bluedroid/ble_50/ble_periodic_adv_with_cte/sdkconfig.defaults @@ -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 diff --git a/examples/bluetooth/bluedroid/ble_50/ble_periodic_sync_with_cte/CMakeLists.txt b/examples/bluetooth/bluedroid/ble_50/ble_periodic_sync_with_cte/CMakeLists.txt new file mode 100644 index 0000000000..718d258563 --- /dev/null +++ b/examples/bluetooth/bluedroid/ble_50/ble_periodic_sync_with_cte/CMakeLists.txt @@ -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) diff --git a/examples/bluetooth/bluedroid/ble_50/ble_periodic_sync_with_cte/README.md b/examples/bluetooth/bluedroid/ble_50/ble_periodic_sync_with_cte/README.md new file mode 100644 index 0000000000..7528592d0e --- /dev/null +++ b/examples/bluetooth/bluedroid/ble_50/ble_periodic_sync_with_cte/README.md @@ -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 +``` + +### 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. + diff --git a/examples/bluetooth/bluedroid/ble_50/ble_periodic_sync_with_cte/main/CMakeLists.txt b/examples/bluetooth/bluedroid/ble_50/ble_periodic_sync_with_cte/main/CMakeLists.txt new file mode 100644 index 0000000000..1a9168bcd5 --- /dev/null +++ b/examples/bluetooth/bluedroid/ble_50/ble_periodic_sync_with_cte/main/CMakeLists.txt @@ -0,0 +1,3 @@ +idf_component_register(SRCS "periodic_sync_with_cte_demo.c" + PRIV_REQUIRES bt nvs_flash + INCLUDE_DIRS ".") diff --git a/examples/bluetooth/bluedroid/ble_50/ble_periodic_sync_with_cte/main/periodic_sync_with_cte_demo.c b/examples/bluetooth/bluedroid/ble_50/ble_periodic_sync_with_cte/main/periodic_sync_with_cte_demo.c new file mode 100644 index 0000000000..e943e47b9a --- /dev/null +++ b/examples/bluetooth/bluedroid/ble_50/ble_periodic_sync_with_cte/main/periodic_sync_with_cte_demo.c @@ -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 +#include +#include +#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; +} diff --git a/examples/bluetooth/bluedroid/ble_50/ble_periodic_sync_with_cte/sdkconfig.defaults b/examples/bluetooth/bluedroid/ble_50/ble_periodic_sync_with_cte/sdkconfig.defaults new file mode 100644 index 0000000000..8e462e0523 --- /dev/null +++ b/examples/bluetooth/bluedroid/ble_50/ble_periodic_sync_with_cte/sdkconfig.defaults @@ -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 diff --git a/examples/bluetooth/bluedroid/ble_50/ble_power_control_central/CMakeLists.txt b/examples/bluetooth/bluedroid/ble_50/ble_power_control_central/CMakeLists.txt new file mode 100644 index 0000000000..3bc3f06582 --- /dev/null +++ b/examples/bluetooth/bluedroid/ble_50/ble_power_control_central/CMakeLists.txt @@ -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) diff --git a/examples/bluetooth/bluedroid/ble_50/ble_power_control_central/README.md b/examples/bluetooth/bluedroid/ble_50/ble_power_control_central/README.md new file mode 100644 index 0000000000..1fa3b88ba3 --- /dev/null +++ b/examples/bluetooth/bluedroid/ble_50/ble_power_control_central/README.md @@ -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 + diff --git a/examples/bluetooth/bluedroid/ble_50/ble_power_control_central/main/CMakeLists.txt b/examples/bluetooth/bluedroid/ble_50/ble_power_control_central/main/CMakeLists.txt new file mode 100644 index 0000000000..f677a03338 --- /dev/null +++ b/examples/bluetooth/bluedroid/ble_50/ble_power_control_central/main/CMakeLists.txt @@ -0,0 +1,3 @@ +idf_component_register(SRCS "main.c" + PRIV_REQUIRES bt nvs_flash + INCLUDE_DIRS ".") diff --git a/examples/bluetooth/bluedroid/ble_50/ble_power_control_central/main/main.c b/examples/bluetooth/bluedroid/ble_50/ble_power_control_central/main/main.c new file mode 100644 index 0000000000..de7c3b8807 --- /dev/null +++ b/examples/bluetooth/bluedroid/ble_50/ble_power_control_central/main/main.c @@ -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 +#include +#include +#include +#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"); +} diff --git a/examples/bluetooth/bluedroid/ble_50/ble_power_control_central/sdkconfig.defaults b/examples/bluetooth/bluedroid/ble_50/ble_power_control_central/sdkconfig.defaults new file mode 100644 index 0000000000..6370d701cd --- /dev/null +++ b/examples/bluetooth/bluedroid/ble_50/ble_power_control_central/sdkconfig.defaults @@ -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 diff --git a/examples/bluetooth/bluedroid/ble_50/ble_power_control_peripheral/CMakeLists.txt b/examples/bluetooth/bluedroid/ble_50/ble_power_control_peripheral/CMakeLists.txt new file mode 100644 index 0000000000..544c08d44b --- /dev/null +++ b/examples/bluetooth/bluedroid/ble_50/ble_power_control_peripheral/CMakeLists.txt @@ -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) diff --git a/examples/bluetooth/bluedroid/ble_50/ble_power_control_peripheral/README.md b/examples/bluetooth/bluedroid/ble_50/ble_power_control_peripheral/README.md new file mode 100644 index 0000000000..cb6310ef8c --- /dev/null +++ b/examples/bluetooth/bluedroid/ble_50/ble_power_control_peripheral/README.md @@ -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 + diff --git a/examples/bluetooth/bluedroid/ble_50/ble_power_control_peripheral/main/CMakeLists.txt b/examples/bluetooth/bluedroid/ble_50/ble_power_control_peripheral/main/CMakeLists.txt new file mode 100644 index 0000000000..f677a03338 --- /dev/null +++ b/examples/bluetooth/bluedroid/ble_50/ble_power_control_peripheral/main/CMakeLists.txt @@ -0,0 +1,3 @@ +idf_component_register(SRCS "main.c" + PRIV_REQUIRES bt nvs_flash + INCLUDE_DIRS ".") diff --git a/examples/bluetooth/bluedroid/ble_50/ble_power_control_peripheral/main/main.c b/examples/bluetooth/bluedroid/ble_50/ble_power_control_peripheral/main/main.c new file mode 100644 index 0000000000..2b1e9728e0 --- /dev/null +++ b/examples/bluetooth/bluedroid/ble_50/ble_power_control_peripheral/main/main.c @@ -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 +#include +#include +#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"); +} diff --git a/examples/bluetooth/bluedroid/ble_50/ble_power_control_peripheral/main/power_control_service.h b/examples/bluetooth/bluedroid/ble_50/ble_power_control_peripheral/main/power_control_service.h new file mode 100644 index 0000000000..f22a60349e --- /dev/null +++ b/examples/bluetooth/bluedroid/ble_50/ble_power_control_peripheral/main/power_control_service.h @@ -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 + +/* 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 */ diff --git a/examples/bluetooth/bluedroid/ble_50/ble_power_control_peripheral/sdkconfig.defaults b/examples/bluetooth/bluedroid/ble_50/ble_power_control_peripheral/sdkconfig.defaults new file mode 100644 index 0000000000..a55da6e362 --- /dev/null +++ b/examples/bluetooth/bluedroid/ble_50/ble_power_control_peripheral/sdkconfig.defaults @@ -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