feat(ble/bluedroid): Add new bluedroid host examples

This commit is contained in:
zhiweijian
2025-12-22 10:19:13 +08:00
parent 0444cf0f5d
commit 2a753a8e30
90 changed files with 6233 additions and 12 deletions

View File

@@ -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

View File

@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2015-2025 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2015-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@@ -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

View File

@@ -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

View File

@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2015-2025 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2015-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@@ -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

View File

@@ -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)

View File

@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2015-2025 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2015-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@@ -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

View File

@@ -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

View File

@@ -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
*

View File

@@ -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;
}

View File

@@ -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);

View File

@@ -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;

View File

@@ -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);

View File

@@ -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, \

View File

@@ -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, \

View File

@@ -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, \

View File

@@ -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

View File

@@ -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 -------------------------------------*/

View File

@@ -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

View File

@@ -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)

View File

@@ -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

View File

@@ -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 -------------------------------------*/

View File

@@ -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

View File

@@ -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 -------------------------------------*/

View File

@@ -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:

View File

@@ -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)

View File

@@ -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

View File

@@ -0,0 +1,3 @@
idf_component_register(SRCS "main.c"
PRIV_REQUIRES bt nvs_flash
INCLUDE_DIRS ".")

View File

@@ -0,0 +1,307 @@
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
/****************************************************************************
*
* This demo showcases BLE Connection Subrating feature on Central side.
* It scans for BLE devices and connects to a peripheral device that supports
* Connection Subrating. After connection, it sets default subrate parameters
* and requests a subrate change to reduce power consumption.
*
****************************************************************************/
#include <stdint.h>
#include <string.h>
#include <stdbool.h>
#include <stdio.h>
#include "nvs.h"
#include "nvs_flash.h"
#include "esp_bt.h"
#include "esp_gap_ble_api.h"
#include "esp_gattc_api.h"
#include "esp_gatt_defs.h"
#include "esp_bt_main.h"
#include "esp_gatt_common_api.h"
#include "esp_log.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#define TAG "BLE_SUBRATING_CENTRAL"
#define REMOTE_DEVICE_NAME "ESP_SUBRATE_PRH"
#define PROFILE_NUM 1
#define PROFILE_A_APP_ID 0
static bool connect = false;
static uint16_t g_conn_handle = 0xFFFF;
static esp_ble_ext_scan_params_t ext_scan_params = {
.own_addr_type = BLE_ADDR_TYPE_PUBLIC,
.filter_policy = BLE_SCAN_FILTER_ALLOW_ALL,
.scan_duplicate = BLE_SCAN_DUPLICATE_DISABLE,
.cfg_mask = ESP_BLE_GAP_EXT_SCAN_CFG_UNCODE_MASK,
.uncoded_cfg = {BLE_SCAN_TYPE_ACTIVE, 0x40, 0x40},
.coded_cfg = {BLE_SCAN_TYPE_ACTIVE, 0x40, 0x40},
};
// Connection parameters for different PHYs
static const esp_ble_conn_params_t phy_1m_conn_params = {
.scan_interval = 0x40,
.scan_window = 0x40,
.interval_min = 0x18, // 30ms
.interval_max = 0x28, // 50ms
.latency = 0,
.supervision_timeout = 0x1F4, // 5s
.min_ce_len = 0,
.max_ce_len = 0,
};
static const esp_ble_conn_params_t phy_2m_conn_params = {
.scan_interval = 0x40,
.scan_window = 0x40,
.interval_min = 0x18,
.interval_max = 0x28,
.latency = 0,
.supervision_timeout = 0x1F4,
.min_ce_len = 0,
.max_ce_len = 0,
};
static const esp_ble_conn_params_t phy_coded_conn_params = {
.scan_interval = 0x40,
.scan_window = 0x40,
.interval_min = 0x18,
.interval_max = 0x28,
.latency = 0,
.supervision_timeout = 0x1F4,
.min_ce_len = 0,
.max_ce_len = 0,
};
struct gattc_profile_inst {
esp_gattc_cb_t gattc_cb;
uint16_t gattc_if;
uint16_t app_id;
uint16_t conn_id;
esp_bd_addr_t remote_bda;
};
static struct gattc_profile_inst gl_profile_tab[PROFILE_NUM] = {
[PROFILE_A_APP_ID] = {
.gattc_cb = NULL,
.gattc_if = ESP_GATT_IF_NONE,
},
};
/**
* @brief GATT client event handler
*/
static void gattc_profile_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param)
{
if (param == NULL) {
ESP_LOGE(TAG, "GATTC event handler received NULL param");
return;
}
esp_ble_gattc_cb_param_t *p_data = (esp_ble_gattc_cb_param_t *)param;
switch (event) {
case ESP_GATTC_REG_EVT:
ESP_LOGI(TAG, "GATT client register, status %d, app_id %d, gattc_if %d",
p_data->reg.status, p_data->reg.app_id, gattc_if);
gl_profile_tab[PROFILE_A_APP_ID].gattc_if = gattc_if;
// Set default subrate parameters
esp_ble_default_subrate_param_t default_subrate_params = {
.subrate_min = 0x0001, // Minimum subrate factor
.subrate_max = 0x000a, // Maximum subrate factor (10x slower)
.max_latency = 0x0009, // Allow 9 intervals of latency
.continuation_number = 0x0008, // Continuation number
.supervision_timeout = 1000, // 10 seconds (1000 * 10ms)
};
esp_err_t ret = esp_ble_gap_set_default_subrate(&default_subrate_params);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Failed to set default subrate, error = 0x%x", ret);
}
break;
case ESP_GATTC_CONNECT_EVT:
ESP_LOGI(TAG, "Connected, conn_id %d, remote "ESP_BD_ADDR_STR"",
p_data->connect.conn_id, ESP_BD_ADDR_HEX(p_data->connect.remote_bda));
gl_profile_tab[PROFILE_A_APP_ID].conn_id = p_data->connect.conn_id;
memcpy(gl_profile_tab[PROFILE_A_APP_ID].remote_bda, p_data->connect.remote_bda, sizeof(esp_bd_addr_t));
g_conn_handle = p_data->connect.conn_handle;
// Request subrate change after setting default parameters
esp_ble_subrate_req_param_t subrate_req_params = {
.conn_handle = g_conn_handle,
.subrate_min = 0x0001, // Minimum subrate factor
.subrate_max = 0x0003, // Maximum subrate factor (3x slower)
.max_latency = 0x0001, // Allow 1 interval of latency
.continuation_number = 0x0002, // Continuation number
.supervision_timeout = 3000, // 30 seconds (3000 * 10ms)
};
esp_err_t subrate_ret = esp_ble_gap_subrate_request(&subrate_req_params);
if (subrate_ret != ESP_OK) {
ESP_LOGE(TAG, "Failed to request subrate, error = 0x%x", subrate_ret);
} else {
ESP_LOGI(TAG, "Subrate request sent successfully");
}
break;
case ESP_GATTC_DISCONNECT_EVT:
connect = false;
g_conn_handle = 0xFFFF;
ESP_LOGI(TAG, "Disconnected, remote "ESP_BD_ADDR_STR", reason 0x%02x",
ESP_BD_ADDR_HEX(p_data->disconnect.remote_bda), p_data->disconnect.reason);
break;
default:
break;
}
}
/**
* @brief GAP event handler
*/
static void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param)
{
if (param == NULL) {
ESP_LOGE(TAG, "GAP event handler received NULL param");
return;
}
uint8_t *adv_name = NULL;
uint8_t adv_name_len = 0;
switch (event) {
case ESP_GAP_BLE_SET_EXT_SCAN_PARAMS_COMPLETE_EVT:
ESP_LOGI(TAG, "Extended scan parameters set, starting scan...");
esp_ble_gap_start_ext_scan(0, 0); // Scan indefinitely
break;
case ESP_GAP_BLE_EXT_SCAN_START_COMPLETE_EVT:
if (param->ext_scan_start.status != ESP_BT_STATUS_SUCCESS) {
ESP_LOGE(TAG, "Extended scan start failed, status %x", param->ext_scan_start.status);
break;
}
ESP_LOGI(TAG, "Extended scan started successfully");
break;
case ESP_GAP_BLE_EXT_ADV_REPORT_EVT: {
adv_name = esp_ble_resolve_adv_data_by_type(param->ext_adv_report.params.adv_data,
param->ext_adv_report.params.adv_data_len,
ESP_BLE_AD_TYPE_NAME_CMPL,
&adv_name_len);
if (adv_name != NULL && adv_name_len > 0) {
ESP_LOGI(TAG, "Found device: "ESP_BD_ADDR_STR", name: %.*s",
ESP_BD_ADDR_HEX(param->ext_adv_report.params.addr), adv_name_len, adv_name);
if (!connect && adv_name_len == strlen(REMOTE_DEVICE_NAME) &&
strncmp((char *)adv_name, REMOTE_DEVICE_NAME, adv_name_len) == 0) {
ESP_LOGI(TAG, "Target device found, connecting...");
connect = true;
esp_ble_gap_stop_ext_scan();
esp_ble_gatt_creat_conn_params_t creat_conn_params = {0};
memcpy(&creat_conn_params.remote_bda, param->ext_adv_report.params.addr, ESP_BD_ADDR_LEN);
creat_conn_params.remote_addr_type = param->ext_adv_report.params.addr_type;
creat_conn_params.own_addr_type = BLE_ADDR_TYPE_PUBLIC;
creat_conn_params.is_direct = true;
creat_conn_params.is_aux = true;
creat_conn_params.phy_mask = ESP_BLE_PHY_1M_PREF_MASK | ESP_BLE_PHY_2M_PREF_MASK | ESP_BLE_PHY_CODED_PREF_MASK;
creat_conn_params.phy_1m_conn_params = &phy_1m_conn_params;
creat_conn_params.phy_2m_conn_params = &phy_2m_conn_params;
creat_conn_params.phy_coded_conn_params = &phy_coded_conn_params;
esp_ble_gattc_enh_open(gl_profile_tab[PROFILE_A_APP_ID].gattc_if, &creat_conn_params);
}
}
break;
}
case ESP_GAP_BLE_EXT_SCAN_STOP_COMPLETE_EVT:
ESP_LOGI(TAG, "Extended scan stopped");
break;
case ESP_GAP_BLE_SET_DEFAULT_SUBRATE_COMPLETE_EVT:
ESP_LOGI(TAG, "Set default subrate complete, status %d", param->set_default_subrate_evt.status);
if (param->set_default_subrate_evt.status == ESP_BT_STATUS_SUCCESS) {
esp_err_t scan_ret = esp_ble_gap_set_ext_scan_params(&ext_scan_params);
if (scan_ret) {
ESP_LOGE(TAG, "set ext scan params error, error code = %x", scan_ret);
}
}
break;
case ESP_GAP_BLE_SUBRATE_REQUEST_COMPLETE_EVT:
ESP_LOGI(TAG, "Subrate request complete, status %d", param->subrate_req_cmpl_evt.status);
break;
case ESP_GAP_BLE_SUBRATE_CHANGE_EVT:
ESP_LOGI(TAG, "Connection subrating update, status %d, conn_hdl %d, subrate_factor %d latency %d, continuation_number %d, timeout %d",
param->subrate_change_evt.status,
param->subrate_change_evt.conn_handle,
param->subrate_change_evt.subrate_factor,
param->subrate_change_evt.peripheral_latency,
param->subrate_change_evt.continuation_number,
param->subrate_change_evt.supervision_timeout);
break;
default:
break;
}
}
void app_main(void)
{
esp_err_t ret;
// Initialize NVS
ret = nvs_flash_init();
if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
ESP_ERROR_CHECK(nvs_flash_erase());
ret = nvs_flash_init();
}
ESP_ERROR_CHECK(ret);
// Initialize Bluetooth Controller
ESP_ERROR_CHECK(esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT));
esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT();
ret = esp_bt_controller_init(&bt_cfg);
if (ret) {
ESP_LOGE(TAG, "Bluetooth controller init failed, error = %x", ret);
return;
}
ret = esp_bt_controller_enable(ESP_BT_MODE_BLE);
if (ret) {
ESP_LOGE(TAG, "Bluetooth controller enable failed, error = %x", ret);
return;
}
// Initialize Bluedroid
esp_bluedroid_config_t cfg = BT_BLUEDROID_INIT_CONFIG_DEFAULT();
ret = esp_bluedroid_init_with_cfg(&cfg);
if (ret) {
ESP_LOGE(TAG, "Bluedroid init failed, error = %x", ret);
return;
}
ret = esp_bluedroid_enable();
if (ret) {
ESP_LOGE(TAG, "Bluedroid enable failed, error = %x", ret);
return;
}
// Register callbacks
ret = esp_ble_gap_register_callback(gap_event_handler);
if (ret) {
ESP_LOGE(TAG, "GAP register callback failed, error = %x", ret);
return;
}
ret = esp_ble_gattc_register_callback(gattc_profile_event_handler);
if (ret) {
ESP_LOGE(TAG, "GATTC register callback failed, error = %x", ret);
return;
}
ret = esp_ble_gattc_app_register(PROFILE_A_APP_ID);
if (ret) {
ESP_LOGE(TAG, "GATTC app register failed, error = %x", ret);
return;
}
ESP_LOGI(TAG, "BLE Connection Subrating Central example started");
}

View File

@@ -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

View File

@@ -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)

View File

@@ -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

View File

@@ -0,0 +1,3 @@
idf_component_register(SRCS "main.c"
PRIV_REQUIRES bt nvs_flash
INCLUDE_DIRS ".")

View File

@@ -0,0 +1,376 @@
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
/****************************************************************************
*
* This demo showcases BLE Connection Subrating feature on Peripheral side.
* It advertises and waits for connection from a central device. After connection,
* it accepts subrate requests from the central to reduce power consumption.
*
****************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "esp_system.h"
#include "esp_log.h"
#include "nvs_flash.h"
#include "esp_bt.h"
#include "esp_gap_ble_api.h"
#include "esp_gatts_api.h"
#include "esp_bt_defs.h"
#include "esp_bt_main.h"
#include "esp_gatt_common_api.h"
#include "subrating_service.h"
#define TAG "BLE_SUBRATING_PERIPHERAL"
#define DEVICE_NAME "ESP_SUBRATE_PRH"
#define PROFILE_NUM 1
#define PROFILE_A_APP_ID 0
#define SVC_INST_ID 0
#define EXT_ADV_HANDLE 0
#define NUM_EXT_ADV 1
#define SEM_WAIT_TIMEOUT_MS 5000
#define FUNC_SEND_WAIT_SEM(func, sem) do {\
esp_err_t __err_rc = (func);\
if (__err_rc != ESP_OK) { \
ESP_LOGE(TAG, "%s, message send fail, error = %d", __func__, __err_rc); \
} \
if (xSemaphoreTake(sem, pdMS_TO_TICKS(SEM_WAIT_TIMEOUT_MS)) != pdTRUE) { \
ESP_LOGE(TAG, "%s, semaphore timeout", __func__); \
} \
} while(0);
static uint16_t conn_handle = 0xFFFF;
uint16_t subrating_handle_table[SUBRATING_IDX_NB];
/* UUIDs */
static const uint16_t primary_service_uuid = ESP_GATT_UUID_PRI_SERVICE;
static const uint16_t character_declaration_uuid = ESP_GATT_UUID_CHAR_DECLARE;
/* Service UUID - must be a variable, not a macro, to take address */
static const uint16_t subrating_service_uuid = BLE_UUID_SUBRATING_SERVICE_VAL;
static const uint16_t subrating_info_char_uuid = BLE_UUID_SUBRATING_SERVICE_VAL;
/* Characteristic properties */
static const uint8_t char_prop_read = ESP_GATT_CHAR_PROP_BIT_READ;
/* Subrating Info value */
static uint8_t subrating_info_value[4] = {0x00, 0x00, 0x00, 0x00};
/* Full Database Description - Used to add attributes into the database */
static const esp_gatts_attr_db_t gatt_db[SUBRATING_IDX_NB] = {
// Service Declaration
[IDX_SUBRATING_SVC] =
{{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&primary_service_uuid, ESP_GATT_PERM_READ,
sizeof(uint16_t), sizeof(subrating_service_uuid), (uint8_t *)&subrating_service_uuid}},
/* Subrating Info Characteristic Declaration */
[IDX_SUBRATING_INFO_CHAR] =
{{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&character_declaration_uuid, ESP_GATT_PERM_READ,
sizeof(uint8_t), sizeof(uint8_t), (uint8_t *)&char_prop_read}},
/* Subrating Info Characteristic Value */
[IDX_SUBRATING_INFO_VAL] =
{{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&subrating_info_char_uuid, ESP_GATT_PERM_READ,
sizeof(subrating_info_value), sizeof(subrating_info_value), (uint8_t *)subrating_info_value}},
};
// Extended advertising data (includes device name)
static uint8_t raw_ext_adv_data[] = {
0x02, ESP_BLE_AD_TYPE_FLAG, 0x06,
0x10, ESP_BLE_AD_TYPE_NAME_CMPL, 'E','S','P','_','S','U','B','R','A','T','E','_','P','R','H'
};
static esp_ble_gap_ext_adv_params_t ext_adv_params = {
.type = ESP_BLE_GAP_SET_EXT_ADV_PROP_CONNECTABLE,
.interval_min = 0x20, // 32 * 0.625ms = 20ms
.interval_max = 0x40, // 64 * 0.625ms = 40ms
.channel_map = ADV_CHNL_ALL,
.filter_policy = ADV_FILTER_ALLOW_SCAN_ANY_CON_ANY,
.primary_phy = ESP_BLE_GAP_PHY_1M,
.max_skip = 0,
.secondary_phy = ESP_BLE_GAP_PHY_1M,
.sid = 0,
.scan_req_notif = false,
.own_addr_type = BLE_ADDR_TYPE_PUBLIC,
.tx_power = EXT_ADV_TX_PWR_NO_PREFERENCE,
};
static esp_ble_gap_ext_adv_t ext_adv[1] = {
[0] = {EXT_ADV_HANDLE, 0, 0},
};
/**
* @brief GAP event handler
*/
static void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param)
{
if (param == NULL) {
ESP_LOGE(TAG, "GAP event handler received NULL param");
return;
}
switch (event) {
case ESP_GAP_BLE_EXT_ADV_SET_PARAMS_COMPLETE_EVT:
ESP_LOGI(TAG, "Extended advertising params set, status %d, instance %d",
param->ext_adv_set_params.status, param->ext_adv_set_params.instance);
if (param->ext_adv_set_params.status == ESP_BT_STATUS_SUCCESS) {
// Set extended advertising data
esp_ble_gap_config_ext_adv_data_raw(EXT_ADV_HANDLE, sizeof(raw_ext_adv_data), raw_ext_adv_data);
} else {
ESP_LOGE(TAG, "Failed to set extended advertising params, status %d", param->ext_adv_set_params.status);
}
break;
case ESP_GAP_BLE_EXT_ADV_DATA_SET_COMPLETE_EVT:
ESP_LOGI(TAG, "Extended advertising data set, status %d, instance %d",
param->ext_adv_data_set.status, param->ext_adv_data_set.instance);
if (param->ext_adv_data_set.status == ESP_BT_STATUS_SUCCESS) {
// Start extended advertising
esp_ble_gap_ext_adv_start(NUM_EXT_ADV, ext_adv);
} else {
ESP_LOGE(TAG, "Failed to set extended advertising data, status %d", param->ext_adv_data_set.status);
}
break;
case ESP_GAP_BLE_EXT_ADV_START_COMPLETE_EVT:
if (param->ext_adv_start.status != ESP_BT_STATUS_SUCCESS) {
ESP_LOGE(TAG, "Extended advertising start failed, status %d", param->ext_adv_start.status);
break;
}
ESP_LOGI(TAG, "Extended advertising start successfully, instance num %d",
param->ext_adv_start.instance_num);
break;
case ESP_GAP_BLE_EXT_ADV_STOP_COMPLETE_EVT:
if (param->ext_adv_stop.status != ESP_BT_STATUS_SUCCESS) {
ESP_LOGE(TAG, "Extended advertising stop failed, status %d", param->ext_adv_stop.status);
break;
}
ESP_LOGI(TAG, "Extended advertising stop successfully");
break;
case ESP_GAP_BLE_UPDATE_CONN_PARAMS_EVT:
ESP_LOGI(TAG, "Connection parameters updated: min_int=%d, max_int=%d, conn_int=%d, latency=%d, timeout=%d",
param->update_conn_params.min_int,
param->update_conn_params.max_int,
param->update_conn_params.conn_int,
param->update_conn_params.latency,
param->update_conn_params.timeout);
break;
case ESP_GAP_BLE_SUBRATE_CHANGE_EVT:
ESP_LOGI(TAG, "Connection subrating update, status %d, conn_hdl %d, subrate_factor %d latency %d, continuation_number %d, timeout %d",
param->subrate_change_evt.status,
param->subrate_change_evt.conn_handle,
param->subrate_change_evt.subrate_factor,
param->subrate_change_evt.peripheral_latency,
param->subrate_change_evt.continuation_number,
param->subrate_change_evt.supervision_timeout);
break;
default:
break;
}
}
struct gatts_profile_inst {
esp_gatts_cb_t gatts_cb;
uint16_t gatts_if;
uint16_t app_id;
uint16_t conn_id;
};
static void gatts_profile_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param);
static struct gatts_profile_inst gl_profile_tab[PROFILE_NUM] = {
[PROFILE_A_APP_ID] = {
.gatts_cb = gatts_profile_event_handler,
.gatts_if = ESP_GATT_IF_NONE,
},
};
/**
* @brief GATTS profile event handler
*/
static void gatts_profile_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param)
{
if (param == NULL) {
ESP_LOGE(TAG, "GATTS event handler received NULL param");
return;
}
switch (event) {
case ESP_GATTS_REG_EVT: {
ESP_LOGI(TAG, "GATT server register, status %d, app_id %d, gatts_if %d",
param->reg.status, param->reg.app_id, gatts_if);
esp_ble_gap_set_device_name(DEVICE_NAME);\
// Set extended advertising parameters
esp_ble_gap_ext_adv_set_params(EXT_ADV_HANDLE, &ext_adv_params);
// Create attribute table
esp_err_t create_attr_ret = esp_ble_gatts_create_attr_tab(gatt_db, gatts_if, SUBRATING_IDX_NB, SVC_INST_ID);
if (create_attr_ret) {
ESP_LOGE(TAG, "create attr table failed, error code = %x", create_attr_ret);
}
break;
}
case ESP_GATTS_CREAT_ATTR_TAB_EVT: {
if (param->add_attr_tab.status != ESP_GATT_OK) {
ESP_LOGE(TAG, "create attribute table failed, error code=0x%x", param->add_attr_tab.status);
} else if (param->add_attr_tab.num_handle != SUBRATING_IDX_NB) {
ESP_LOGE(TAG, "create attribute table abnormally, num_handle (%d) doesn't equal to SUBRATING_IDX_NB(%d)",
param->add_attr_tab.num_handle, SUBRATING_IDX_NB);
} else {
ESP_LOGI(TAG, "create attribute table successfully, the number handle = %d", param->add_attr_tab.num_handle);
// Validate handles pointer before memcpy
if (param->add_attr_tab.handles) {
memcpy(subrating_handle_table, param->add_attr_tab.handles, sizeof(subrating_handle_table));
esp_ble_gatts_start_service(subrating_handle_table[IDX_SUBRATING_SVC]);
} else {
ESP_LOGE(TAG, "Invalid handles pointer");
}
}
break;
}
case ESP_GATTS_START_EVT:
ESP_LOGI(TAG, "Service start, status %d, service_handle %d",
param->start.status, param->start.service_handle);
break;
case ESP_GATTS_CONNECT_EVT: {
ESP_LOGI(TAG, "Connected, conn_id %u, remote "ESP_BD_ADDR_STR"",
param->connect.conn_id, ESP_BD_ADDR_HEX(param->connect.remote_bda));
gl_profile_tab[PROFILE_A_APP_ID].conn_id = param->connect.conn_id;
conn_handle = param->connect.conn_id;
esp_ble_conn_update_params_t conn_params = {0};
memcpy(conn_params.bda, param->connect.remote_bda, sizeof(esp_bd_addr_t));
conn_params.latency = 0;
conn_params.max_int = 0x20;
conn_params.min_int = 0x10;
conn_params.timeout = 400;
esp_ble_gap_update_conn_params(&conn_params);
// Stop extended advertising
uint8_t adv_handle = EXT_ADV_HANDLE;
esp_ble_gap_ext_adv_stop(1, &adv_handle);
break;
}
case ESP_GATTS_DISCONNECT_EVT:
ESP_LOGI(TAG, "Disconnected, remote "ESP_BD_ADDR_STR", reason 0x%02x",
ESP_BD_ADDR_HEX(param->disconnect.remote_bda), param->disconnect.reason);
conn_handle = 0xFFFF;
// Restart extended advertising
esp_ble_gap_ext_adv_start(NUM_EXT_ADV, ext_adv);
break;
case ESP_GATTS_READ_EVT:
ESP_LOGI(TAG, "Read event, handle %d", param->read.handle);
// Auto response is enabled, so no need to send response manually
break;
case ESP_GATTS_WRITE_EVT:
ESP_LOGI(TAG, "Write event, handle %d, len %d", param->write.handle, param->write.len);
// Auto response is enabled, so no need to send response manually
break;
case ESP_GATTS_MTU_EVT:
ESP_LOGI(TAG, "MTU exchange, MTU %d", param->mtu.mtu);
break;
default:
break;
}
}
static void esp_gatts_cb(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param)
{
if (event == ESP_GATTS_REG_EVT) {
if (param->reg.status == ESP_GATT_OK) {
gl_profile_tab[param->reg.app_id].gatts_if = gatts_if;
} else {
ESP_LOGI(TAG, "reg app failed, app_id %04x, status %d",
param->reg.app_id, param->reg.status);
return;
}
}
do {
int idx;
for (idx = 0; idx < PROFILE_NUM; idx++) {
if (gatts_if == ESP_GATT_IF_NONE ||
gatts_if == gl_profile_tab[idx].gatts_if) {
if (gl_profile_tab[idx].gatts_cb) {
gl_profile_tab[idx].gatts_cb(event, gatts_if, param);
}
}
}
} while (0);
}
void app_main(void)
{
esp_err_t ret;
// Initialize NVS
ret = nvs_flash_init();
if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
ESP_ERROR_CHECK(nvs_flash_erase());
ret = nvs_flash_init();
}
ESP_ERROR_CHECK(ret);
// Initialize Bluetooth Controller
ESP_ERROR_CHECK(esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT));
esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT();
ret = esp_bt_controller_init(&bt_cfg);
if (ret) {
ESP_LOGE(TAG, "Bluetooth controller init failed, error = %x", ret);
return;
}
ret = esp_bt_controller_enable(ESP_BT_MODE_BLE);
if (ret) {
ESP_LOGE(TAG, "Bluetooth controller enable failed, error = %x", ret);
return;
}
// Initialize Bluedroid
esp_bluedroid_config_t cfg = BT_BLUEDROID_INIT_CONFIG_DEFAULT();
ret = esp_bluedroid_init_with_cfg(&cfg);
if (ret) {
ESP_LOGE(TAG, "Bluedroid init failed, error = %x", ret);
return;
}
ret = esp_bluedroid_enable();
if (ret) {
ESP_LOGE(TAG, "Bluedroid enable failed, error = %x", ret);
return;
}
// Register callbacks
ret = esp_ble_gap_register_callback(gap_event_handler);
if (ret) {
ESP_LOGE(TAG, "GAP register callback failed, error = %x", ret);
return;
}
ret = esp_ble_gatts_register_callback(esp_gatts_cb);
if (ret) {
ESP_LOGE(TAG, "gatts register error, error code = %x", ret);
return;
}
ret = esp_ble_gatts_app_register(PROFILE_A_APP_ID);
if (ret) {
ESP_LOGE(TAG, "gatts app register error, error code = %x", ret);
return;
}
ESP_LOGI(TAG, "BLE Connection Subrating Peripheral example started");
}

View File

@@ -0,0 +1,25 @@
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
#ifndef SUBRATING_SERVICE_H
#define SUBRATING_SERVICE_H
#include <stdint.h>
/* Subrating Service UUIDs */
#define BLE_UUID_SUBRATING_SERVICE_VAL 0xFF00
/* Attributes State Machine */
enum {
IDX_SUBRATING_SVC,
/* Subrating Info Characteristic */
IDX_SUBRATING_INFO_CHAR,
IDX_SUBRATING_INFO_VAL,
SUBRATING_IDX_NB,
};
#endif /* SUBRATING_SERVICE_H */

View File

@@ -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

View File

@@ -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)

View File

@@ -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.

View File

@@ -0,0 +1,3 @@
idf_component_register(SRCS "connection_central_with_cte.c"
PRIV_REQUIRES bt nvs_flash
INCLUDE_DIRS ".")

View File

@@ -0,0 +1,611 @@
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
/****************************************************************************
*
* This file is for gatt_security_client demo. It can scan ble device, connect one device that needs to be encrypted.
* run gatt_security_server demo, the gatt_security_client demo will automatically connect the gatt_security_server,
* then paring and bonding.
*
****************************************************************************/
#include <stdint.h>
#include <string.h>
#include <stdbool.h>
#include <stdio.h>
#include <inttypes.h>
#include "nvs.h"
#include "nvs_flash.h"
#include "esp_bt.h"
#include "esp_gap_ble_api.h"
#include "esp_gattc_api.h"
#include "esp_gatt_defs.h"
#include "esp_bt_main.h"
#include "esp_gatt_common_api.h"
#include "esp_log.h"
#include "freertos/FreeRTOS.h"
#include "esp_ble_cte_api.h"
#define LOG_TAG "CTE_TEST"
#define REMOTE_SERVICE_UUID 0x00FF
#define REMOTE_NOTIFY_UUID 0xFF01
#define EXT_SCAN_DURATION 0
#define EXT_SCAN_PERIOD 0
///Declare static functions
static void esp_gap_cb(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param);
static void esp_gattc_cb(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param);
static void gattc_profile_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param);
static esp_bt_uuid_t remote_filter_service_uuid = {
.len = ESP_UUID_LEN_16,
.uuid = {.uuid16 = REMOTE_SERVICE_UUID,},
};
static bool connect = false;
static bool get_service = false;
static char remote_device_name[ESP_BLE_ADV_NAME_LEN_MAX] = "ESP_CON_CTE_TEST";
static esp_ble_ext_scan_params_t ext_scan_params = {
.own_addr_type = BLE_ADDR_TYPE_PUBLIC,
.filter_policy = BLE_SCAN_FILTER_ALLOW_ALL,
.scan_duplicate = BLE_SCAN_DUPLICATE_DISABLE,
.cfg_mask = ESP_BLE_GAP_EXT_SCAN_CFG_UNCODE_MASK | ESP_BLE_GAP_EXT_SCAN_CFG_CODE_MASK,
.uncoded_cfg = {BLE_SCAN_TYPE_ACTIVE, 40, 40},
.coded_cfg = {BLE_SCAN_TYPE_ACTIVE, 40, 40},
};
const esp_ble_conn_params_t phy_1m_conn_params = {
.scan_interval = 0x40,
.scan_window = 0x40,
.interval_min = 320,
.interval_max = 320,
.latency = 0,
.supervision_timeout = 600,
.min_ce_len = 0,
.max_ce_len = 0,
};
const esp_ble_conn_params_t phy_2m_conn_params = {
.scan_interval = 0x40,
.scan_window = 0x40,
.interval_min = 320,
.interval_max = 320,
.latency = 0,
.supervision_timeout = 600,
.min_ce_len = 0,
.max_ce_len = 0,
};
const esp_ble_conn_params_t phy_coded_conn_params = {
.scan_interval = 0x40,
.scan_window = 0x40,
.interval_min = 320, // 306-> 362Kbps
.interval_max = 320,
.latency = 0,
.supervision_timeout = 600,
.min_ce_len = 0,
.max_ce_len = 0,
};
static uint8_t antenna_ids[2] = {0x00, 0x01};
esp_ble_cte_recv_params_params_t cte_recv_params = {
.conn_handle = 0xff,
.sampling_en = ESP_BLE_CTE_SAMPLING_ENABLE,
.slot_dur = ESP_BLE_CTE_SLOT_DURATION_2US,
.switching_pattern_len = sizeof(antenna_ids),
.antenna_ids = &antenna_ids[0],
};
static esp_ble_cte_req_en_params_t cte_conn_req_en = {
.conn_handle = 0xff,
.enable = ESP_BLE_CTE_SAMPLING_ENABLE,
.cte_req_interval = 0x05,
.req_cte_len = ESP_BLE_CTE_MAX_REQUESTED_CTE_LENGTH,
.req_cte_Type = ESP_BLE_CTE_TYPE_AOA,
};
uint16_t cur_conn_hdl = 0xff;
#define PROFILE_NUM 1
#define PROFILE_A_APP_ID 0
#define INVALID_HANDLE 0
struct gattc_profile_inst {
esp_gattc_cb_t gattc_cb;
uint16_t gattc_if;
uint16_t app_id;
uint16_t conn_id;
uint16_t service_start_handle;
uint16_t service_end_handle;
uint16_t notify_char_handle;
esp_bd_addr_t remote_bda;
};
/* One gatt-based profile one app_id and one gattc_if, this array will store the gattc_if returned by ESP_GATTS_REG_EVT */
static struct gattc_profile_inst gl_profile_tab[PROFILE_NUM] = {
[PROFILE_A_APP_ID] = {
.gattc_cb = gattc_profile_event_handler,
.gattc_if = ESP_GATT_IF_NONE, /* Not get the gatt_if, so initial is ESP_GATT_IF_NONE */
},
};
static const char *esp_key_type_to_str(esp_ble_key_type_t key_type)
{
const char *key_str = NULL;
switch(key_type) {
case ESP_LE_KEY_NONE:
key_str = "ESP_LE_KEY_NONE";
break;
case ESP_LE_KEY_PENC:
key_str = "ESP_LE_KEY_PENC";
break;
case ESP_LE_KEY_PID:
key_str = "ESP_LE_KEY_PID";
break;
case ESP_LE_KEY_PCSRK:
key_str = "ESP_LE_KEY_PCSRK";
break;
case ESP_LE_KEY_PLK:
key_str = "ESP_LE_KEY_PLK";
break;
case ESP_LE_KEY_LLK:
key_str = "ESP_LE_KEY_LLK";
break;
case ESP_LE_KEY_LENC:
key_str = "ESP_LE_KEY_LENC";
break;
case ESP_LE_KEY_LID:
key_str = "ESP_LE_KEY_LID";
break;
case ESP_LE_KEY_LCSRK:
key_str = "ESP_LE_KEY_LCSRK";
break;
default:
key_str = "INVALID BLE KEY TYPE";
break;
}
return key_str;
}
static char *esp_auth_req_to_str(esp_ble_auth_req_t auth_req)
{
char *auth_str = NULL;
switch(auth_req) {
case ESP_LE_AUTH_NO_BOND:
auth_str = "ESP_LE_AUTH_NO_BOND";
break;
case ESP_LE_AUTH_BOND:
auth_str = "ESP_LE_AUTH_BOND";
break;
case ESP_LE_AUTH_REQ_MITM:
auth_str = "ESP_LE_AUTH_REQ_MITM";
break;
case ESP_LE_AUTH_REQ_BOND_MITM:
auth_str = "ESP_LE_AUTH_REQ_BOND_MITM";
break;
case ESP_LE_AUTH_REQ_SC_ONLY:
auth_str = "ESP_LE_AUTH_REQ_SC_ONLY";
break;
case ESP_LE_AUTH_REQ_SC_BOND:
auth_str = "ESP_LE_AUTH_REQ_SC_BOND";
break;
case ESP_LE_AUTH_REQ_SC_MITM:
auth_str = "ESP_LE_AUTH_REQ_SC_MITM";
break;
case ESP_LE_AUTH_REQ_SC_MITM_BOND:
auth_str = "ESP_LE_AUTH_REQ_SC_MITM_BOND";
break;
default:
auth_str = "INVALID BLE AUTH REQ";
break;
}
return auth_str;
}
static void gattc_profile_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param)
{
esp_ble_gattc_cb_param_t *p_data = (esp_ble_gattc_cb_param_t *)param;
switch (event) {
case ESP_GATTC_REG_EVT:
ESP_LOGI(LOG_TAG, "GATT client register, status %u", p_data->reg.status);
esp_ble_gap_config_local_privacy(true);
break;
case ESP_GATTC_CONNECT_EVT:
ESP_LOGI(LOG_TAG, "Connected, cur_conn_hdl %d conn_id %d, remote "ESP_BD_ADDR_STR"", p_data->connect.conn_handle,
p_data->connect.conn_id, ESP_BD_ADDR_HEX(p_data->connect.remote_bda));
cur_conn_hdl = p_data->connect.conn_handle;
break;
case ESP_GATTC_OPEN_EVT:
if (param->open.status != ESP_GATT_OK){
ESP_LOGE(LOG_TAG, "Open failed, status %x", p_data->open.status);
break;
}
ESP_LOGI(LOG_TAG, "Open successfully, MTU %d", p_data->open.mtu);
gl_profile_tab[PROFILE_A_APP_ID].conn_id = p_data->open.conn_id;
memcpy(gl_profile_tab[PROFILE_A_APP_ID].remote_bda, p_data->open.remote_bda, sizeof(esp_bd_addr_t));
esp_err_t mtu_ret = esp_ble_gattc_send_mtu_req (gattc_if, p_data->open.conn_id);
if (mtu_ret){
ESP_LOGE(LOG_TAG, "config MTU error, error code = %x", mtu_ret);
}
break;
case ESP_GATTC_CFG_MTU_EVT:
ESP_LOGI(LOG_TAG, "MTU exchange, status %d, MTU %d, conn_id %d", param->cfg_mtu.status, param->cfg_mtu.mtu, param->cfg_mtu.conn_id);
if (!param->cfg_mtu.status) {
ESP_LOGI(LOG_TAG, "Set CTE connection receive params, conn_handle %d", cur_conn_hdl);
cte_recv_params.conn_handle = cur_conn_hdl;
esp_ble_cte_set_connection_receive_params(&cte_recv_params);
}
break;
case ESP_GATTC_DIS_SRVC_CMPL_EVT:
if (param->dis_srvc_cmpl.status != ESP_GATT_OK){
ESP_LOGE(LOG_TAG, "Service discover failed, status %d", param->dis_srvc_cmpl.status);
break;
}
ESP_LOGI(LOG_TAG, "Service discover complete, conn_id %d", param->dis_srvc_cmpl.conn_id);
esp_ble_gattc_search_service(gattc_if, param->cfg_mtu.conn_id, &remote_filter_service_uuid);
break;
case ESP_GATTC_SEARCH_RES_EVT: {
ESP_LOGI(LOG_TAG, "Service search result, conn_id %x, is primary service %d", p_data->search_res.conn_id, p_data->search_res.is_primary);
ESP_LOGI(LOG_TAG, "start handle %d end handle %d current handle value %d", p_data->search_res.start_handle, p_data->search_res.end_handle, p_data->search_res.srvc_id.inst_id);
if (p_data->search_res.srvc_id.uuid.len == ESP_UUID_LEN_16 && p_data->search_res.srvc_id.uuid.uuid.uuid16 == REMOTE_SERVICE_UUID) {
ESP_LOGI(LOG_TAG, "UUID16: %x", p_data->search_res.srvc_id.uuid.uuid.uuid16);
get_service = true;
gl_profile_tab[PROFILE_A_APP_ID].service_start_handle = p_data->search_res.start_handle;
gl_profile_tab[PROFILE_A_APP_ID].service_end_handle = p_data->search_res.end_handle;
}
break;
}
case ESP_GATTC_SEARCH_CMPL_EVT:
if (p_data->search_cmpl.status != ESP_GATT_OK){
ESP_LOGE(LOG_TAG, "Service search failed, status %x", p_data->search_cmpl.status);
break;
}
ESP_LOGI(LOG_TAG, "Service search complete");
if(p_data->search_cmpl.searched_service_source == ESP_GATT_SERVICE_FROM_REMOTE_DEVICE) {
ESP_LOGI(LOG_TAG, "Get service information from remote device");
} else if (p_data->search_cmpl.searched_service_source == ESP_GATT_SERVICE_FROM_NVS_FLASH) {
ESP_LOGI(LOG_TAG, "Get service information from flash");
} else {
ESP_LOGI(LOG_TAG, "unknown service source");
}
break;
case ESP_GATTC_REG_FOR_NOTIFY_EVT: {
if (p_data->reg_for_notify.status != ESP_GATT_OK){
ESP_LOGE(LOG_TAG, "Notification register failed, status %x", p_data->reg_for_notify.status);
break;
}
ESP_LOGI(LOG_TAG, "Notification register successfully");
break;
}
case ESP_GATTC_NOTIFY_EVT:
ESP_LOGI(LOG_TAG, "Notification received, value ");
ESP_LOG_BUFFER_HEX(LOG_TAG, p_data->notify.value, p_data->notify.value_len);
break;
case ESP_GATTC_WRITE_DESCR_EVT:
if (p_data->write.status != ESP_GATT_OK){
ESP_LOGE(LOG_TAG, "Descriptor write failed, status %x", p_data->write.status);
break;
}
ESP_LOGI(LOG_TAG, "Descriptor write successfully");
break;
case ESP_GATTC_SRVC_CHG_EVT: {
esp_bd_addr_t bda;
memcpy(bda, p_data->srvc_chg.remote_bda, sizeof(esp_bd_addr_t));
ESP_LOGI(LOG_TAG, "Service change from "ESP_BD_ADDR_STR"", ESP_BD_ADDR_HEX(bda));
break;
}
case ESP_GATTC_WRITE_CHAR_EVT:
if (p_data->write.status != ESP_GATT_OK){
ESP_LOGE(LOG_TAG, "Characteristic write failed, status %x", p_data->write.status);
break;
}
ESP_LOGI(LOG_TAG, "Characteristic Write successfully");
break;
case ESP_GATTC_DISCONNECT_EVT:
ESP_LOGI(LOG_TAG, "Disconnected, remote "ESP_BD_ADDR_STR", reason 0x%02x",
ESP_BD_ADDR_HEX(p_data->disconnect.remote_bda), p_data->disconnect.reason);
connect = false;
get_service = false;
break;
default:
break;
}
}
static void esp_gap_cb(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param)
{
switch (event) {
case ESP_GAP_BLE_SET_LOCAL_PRIVACY_COMPLETE_EVT:
if (param->local_privacy_cmpl.status != ESP_BT_STATUS_SUCCESS){
ESP_LOGE(LOG_TAG, "Local privacy config failed, status %x", param->local_privacy_cmpl.status);
break;
}
esp_err_t scan_ret = esp_ble_gap_set_ext_scan_params(&ext_scan_params);
if (scan_ret){
ESP_LOGE(LOG_TAG, "Set extend scan params error, error code = %x", scan_ret);
}
break;
case ESP_GAP_BLE_SET_EXT_SCAN_PARAMS_COMPLETE_EVT: {
if (param->set_ext_scan_params.status != ESP_BT_STATUS_SUCCESS) {
ESP_LOGE(LOG_TAG, "Extend scan parameters set failed, error status = %x", param->set_ext_scan_params.status);
break;
}
//the unit of the duration is second
esp_ble_gap_start_ext_scan(EXT_SCAN_DURATION, EXT_SCAN_PERIOD);
break;
}
case ESP_GAP_BLE_EXT_SCAN_START_COMPLETE_EVT:
if (param->ext_scan_start.status != ESP_BT_STATUS_SUCCESS) {
ESP_LOGE(LOG_TAG, "Extended scanning start failed, status %x", param->ext_scan_start.status);
break;
}
ESP_LOGI(LOG_TAG, "Extended scanning start successfully");
break;
case ESP_GAP_BLE_PASSKEY_REQ_EVT: /* passkey request event */
/* Call the following function to input the passkey which is displayed on the remote device */
//esp_ble_passkey_reply(gl_profile_tab[PROFILE_A_APP_ID].remote_bda, true, 0x00);
ESP_LOGI(LOG_TAG, "Passkey request");
break;
case ESP_GAP_BLE_OOB_REQ_EVT: {
ESP_LOGI(LOG_TAG, "OOB request");
uint8_t tk[16] = {1}; //If you paired with OOB, both devices need to use the same tk
esp_ble_oob_req_reply(param->ble_security.ble_req.bd_addr, tk, sizeof(tk));
break;
}
case ESP_GAP_BLE_LOCAL_IR_EVT: /* BLE local IR event */
ESP_LOGI(LOG_TAG, "Local identity root");
break;
case ESP_GAP_BLE_LOCAL_ER_EVT: /* BLE local ER event */
ESP_LOGI(LOG_TAG, "Local encryption root");
break;
case ESP_GAP_BLE_SEC_REQ_EVT:
/* send the positive(true) security response to the peer device to accept the security request.
If not accept the security request, should send the security response with negative(false) accept value*/
esp_ble_gap_security_rsp(param->ble_security.ble_req.bd_addr, true);
break;
case ESP_GAP_BLE_NC_REQ_EVT:
/* The app will receive this evt when the IO has DisplayYesNO capability and the peer device IO also has DisplayYesNo capability.
show the passkey number to the user to confirm it with the number displayed by peer device. */
esp_ble_confirm_reply(param->ble_security.ble_req.bd_addr, true);
ESP_LOGI(LOG_TAG, "Numeric Comparison request, passkey %" PRIu32, param->ble_security.key_notif.passkey);
break;
case ESP_GAP_BLE_PASSKEY_NOTIF_EVT: ///the app will receive this evt when the IO has Output capability and the peer device IO has Input capability.
///show the passkey number to the user to input it in the peer device.
ESP_LOGI(LOG_TAG, "Passkey notify, passkey %06" PRIu32, param->ble_security.key_notif.passkey);
break;
case ESP_GAP_BLE_KEY_EVT:
//shows the ble key info share with peer device to the user.
ESP_LOGI(LOG_TAG, "Key exchanged, key_type %s", esp_key_type_to_str(param->ble_security.ble_key.key_type));
break;
case ESP_GAP_BLE_AUTH_CMPL_EVT: {
esp_bd_addr_t bd_addr;
memcpy(bd_addr, param->ble_security.auth_cmpl.bd_addr, sizeof(esp_bd_addr_t));
ESP_LOGI(LOG_TAG, "Authentication complete, addr_type %d, addr "ESP_BD_ADDR_STR"",
param->ble_security.auth_cmpl.addr_type, ESP_BD_ADDR_HEX(bd_addr));
if (!param->ble_security.auth_cmpl.success) {
ESP_LOGI(LOG_TAG, "Pairing failed, reason 0x%x",param->ble_security.auth_cmpl.fail_reason);
} else {
ESP_LOGI(LOG_TAG, "Pairing successfully, auth mode %s",esp_auth_req_to_str(param->ble_security.auth_cmpl.auth_mode));
// Enable CTE
}
break;
}
case ESP_GAP_BLE_EXT_ADV_REPORT_EVT: {
uint8_t *adv_name = NULL;
uint8_t adv_name_len = 0;
adv_name = esp_ble_resolve_adv_data_by_type(param->ext_adv_report.params.adv_data,
param->ext_adv_report.params.adv_data_len,
ESP_BLE_AD_TYPE_NAME_CMPL,
&adv_name_len);
if (!connect && strlen(remote_device_name) == adv_name_len && strncmp((char *)adv_name, remote_device_name, adv_name_len) == 0) {
// Note: If there are multiple devices with the same device name, the device may connect to an unintended one.
// It is recommended to change the default device name to ensure it is unique.
connect = true;
esp_ble_gap_stop_ext_scan();
ESP_LOGI(LOG_TAG, "Device found "ESP_BD_ADDR_STR"", ESP_BD_ADDR_HEX(param->ext_adv_report.params.addr));
ESP_LOG_BUFFER_CHAR("Adv name", adv_name, adv_name_len);
ESP_LOGI(LOG_TAG, "Stop extend scan and create aux open, primary_phy %d secondary phy %d", param->ext_adv_report.params.primary_phy, param->ext_adv_report.params.secondly_phy);
// create gattc virtual connection
esp_ble_gatt_creat_conn_params_t creat_conn_params = {0};
memcpy(&creat_conn_params.remote_bda, param->ext_adv_report.params.addr, ESP_BD_ADDR_LEN);
creat_conn_params.remote_addr_type = param->ext_adv_report.params.addr_type;
creat_conn_params.own_addr_type = BLE_ADDR_TYPE_PUBLIC;
creat_conn_params.is_direct = true;
creat_conn_params.is_aux = true;
creat_conn_params.phy_mask = ESP_BLE_PHY_1M_PREF_MASK | ESP_BLE_PHY_2M_PREF_MASK | ESP_BLE_PHY_CODED_PREF_MASK;
creat_conn_params.phy_1m_conn_params = &phy_1m_conn_params;
creat_conn_params.phy_2m_conn_params = &phy_2m_conn_params;
creat_conn_params.phy_coded_conn_params = &phy_coded_conn_params;
esp_ble_gattc_enh_open(gl_profile_tab[PROFILE_A_APP_ID].gattc_if, &creat_conn_params);
}
break;
}
case ESP_GAP_BLE_EXT_SCAN_STOP_COMPLETE_EVT:
if (param->ext_scan_stop.status != ESP_BT_STATUS_SUCCESS){
ESP_LOGE(LOG_TAG, "Scanning stop failed, status %x", param->ext_scan_stop.status);
break;
}
ESP_LOGI(LOG_TAG, "Scanning stop successfully");
break;
default:
break;
}
}
static void cte_event_handler(esp_ble_cte_cb_event_t event, esp_ble_cte_cb_param_t *param)
{
switch (event) {
case ESP_BLE_CTE_SET_CONN_TRANS_PARAMS_CMPL_EVT:
ESP_LOGI(LOG_TAG, "CTE set connection transmit params, status %d", param->conn_trans_params_cmpl.status);
break;
case ESP_BLE_CTE_SET_CONN_RECV_PARAMS_CMPL_EVT:
ESP_LOGI(LOG_TAG, "CTE set connection receive params, status %d", param->conn_recv_params_cmpl.status);
if (!param->conn_recv_params_cmpl.status) {
cte_conn_req_en.conn_handle = cur_conn_hdl;
ESP_LOGI(LOG_TAG, "Enable CTE request, conn_handle %d", cur_conn_hdl);
esp_ble_cte_connection_cte_request_enable(&cte_conn_req_en);
}
break;
case ESP_BLE_CTE_SET_CONN_REQ_ENABLE_CMPL_EVT:
ESP_LOGI(LOG_TAG, "CTE set connection request enable, status %d", param->conn_req_en_cmpl.status);
break;
case ESP_BLE_CTE_SET_CONN_RSP_ENABLE_CMPL_EVT:
ESP_LOGI(LOG_TAG, "CTE set connection response enable, status %d", param->conn_rsp_en_cmpl.status);
break;
case ESP_BLE_CTE_READ_ANT_INFOR_CMPL_EVT:
ESP_LOGI(LOG_TAG, "CTE read antenna information, status %d antenna_num %d", param->read_ant_infor_cmpl.status, param->read_ant_infor_cmpl.num_antennae);
break;
case ESP_BLE_CTE_CONN_IQ_REPORT_EVT:
ESP_LOGI(LOG_TAG, "conn_handle %d channel_idx %d rssi %d rssi_ant_id %d cte_type %d slot_dur %d pkt_status %d conn_evt_counter %d sample_count %d",
param->conn_iq_rpt.conn_handle, param->conn_iq_rpt.data_channel_idx, param->conn_iq_rpt.rssi,
param->conn_iq_rpt.rssi_ant_id, param->conn_iq_rpt.cte_type, param->conn_iq_rpt.slot_dur,
param->conn_iq_rpt.pkt_status, param->conn_iq_rpt.conn_evt_counter, param->conn_iq_rpt.sample_count);
ESP_LOG_BUFFER_HEX("i_sample", &param->conn_iq_rpt.i_sample[0], param->conn_iq_rpt.sample_count);
ESP_LOG_BUFFER_HEX("q_sample", &param->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));
}

View File

@@ -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

View File

@@ -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)

View File

@@ -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.

View File

@@ -0,0 +1,3 @@
idf_component_register(SRCS "connection_peripheral_with_cte.c"
PRIV_REQUIRES bt nvs_flash
INCLUDE_DIRS ".")

View File

@@ -0,0 +1,567 @@
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
#include <inttypes.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/event_groups.h"
#include "esp_system.h"
#include "esp_log.h"
#include "nvs_flash.h"
#include "esp_bt.h"
#include "esp_gap_ble_api.h"
#include "esp_gatts_api.h"
#include "esp_bt_defs.h"
#include "esp_bt_main.h"
#include "connection_peripheral_with_cte.h"
#include "esp_ble_cte_api.h"
#define LOG_TAG "CTE_TEST"
#define HEART_PROFILE_NUM 1
#define HEART_PROFILE_APP_IDX 0
#define ESP_HEART_RATE_APP_ID 0x55
#define HEART_RATE_SVC_INST_ID 0
#define EXT_ADV_HANDLE 0
#define NUM_EXT_ADV_SET 1
#define EXT_ADV_DURATION 0
#define EXT_ADV_MAX_EVENTS 0
#define EXT_ADV_NAME_LEN_OFFSET 10
#define EXT_ADV_NAME_OFFSET 12
#define GATTS_DEMO_CHAR_VAL_LEN_MAX 0x40
#ifndef MIN
#define MIN(a, b) (((a) < (b)) ? (a) : (b))
#endif
static uint16_t profile_handle_table[HRS_IDX_NB];
static uint8_t ext_adv_raw_data[] = {
0x02, 0x01, 0x06,
0x02, 0x0a, 0xeb, 0x03, 0x03, 0xab, 0xcd,
0x11, 0X09, 'E', 'S', 'P', '_', 'C', 'O', 'N', '_', 'C', 'T', 'E', '_', 'T', 'E', 'S', 'T',
};
static esp_ble_gap_ext_adv_t ext_adv[1] = {
[0] = {EXT_ADV_HANDLE, EXT_ADV_DURATION, EXT_ADV_MAX_EVENTS},
};
esp_ble_gap_ext_adv_params_t ext_adv_params_2M = {
.type = ESP_BLE_GAP_SET_EXT_ADV_PROP_CONNECTABLE,
.interval_min = 0x20,
.interval_max = 0x20,
.channel_map = ADV_CHNL_ALL,
.filter_policy = ADV_FILTER_ALLOW_SCAN_ANY_CON_ANY,
.primary_phy = ESP_BLE_GAP_PHY_1M,
.max_skip = 0,
.secondary_phy = ESP_BLE_GAP_PHY_2M,
.sid = 0,
.scan_req_notif = false,
.own_addr_type = BLE_ADDR_TYPE_PUBLIC,
.tx_power = EXT_ADV_TX_PWR_NO_PREFERENCE,
};
static uint8_t antenna_ids[2] = {0x00, 0x01};
static esp_ble_cte_conn_trans_params_t cte_conn_trans_params = {
.conn_handle = 0xff,
.cte_types = ESP_BLE_CTE_TYPES_ALL,
.switching_pattern_len = sizeof(antenna_ids),
.antenna_ids = &antenna_ids[0],
};
static esp_ble_cte_rsp_en_params_t cte_conn_rsp_en = {
.conn_handle = 0xff,
.enable = ESP_BLE_CTE_RESPONSE_FOR_CONNECTION_ENABLE,
};
uint16_t cur_conn_hdl = 0xff;
struct gatts_profile_inst {
esp_gatts_cb_t gatts_cb;
uint16_t gatts_if;
uint16_t app_id;
uint16_t conn_id;
uint16_t service_handle;
esp_gatt_srvc_id_t service_id;
uint16_t char_handle;
esp_bt_uuid_t char_uuid;
esp_gatt_perm_t perm;
esp_gatt_char_prop_t property;
uint16_t descr_handle;
esp_bt_uuid_t descr_uuid;
};
static void gatts_profile_event_handler(esp_gatts_cb_event_t event,
esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param);
/* One gatt-based profile one app_id and one gatts_if, this array will store the gatts_if returned by ESP_GATTS_REG_EVT */
static struct gatts_profile_inst heart_rate_profile_tab[HEART_PROFILE_NUM] = {
[HEART_PROFILE_APP_IDX] = {
.gatts_cb = gatts_profile_event_handler,
.gatts_if = ESP_GATT_IF_NONE, /* Not get the gatt_if, so initial is ESP_GATT_IF_NONE */
},
};
/* Service */
static const uint16_t GATTS_SERVICE_UUID_TEST = 0x00FF;
static const uint16_t GATTS_CHAR_UUID_TEST_A = 0xFF01;
static const uint16_t GATTS_CHAR_UUID_TEST_B = 0xFF02;
static const uint16_t GATTS_CHAR_UUID_TEST_C = 0xFF03;
static const uint16_t primary_service_uuid = ESP_GATT_UUID_PRI_SERVICE;
static const uint16_t character_declaration_uuid = ESP_GATT_UUID_CHAR_DECLARE;
static const uint16_t character_client_config_uuid = ESP_GATT_UUID_CHAR_CLIENT_CONFIG;
static const uint8_t char_prop_read = ESP_GATT_CHAR_PROP_BIT_READ;
static const uint8_t char_prop_write = ESP_GATT_CHAR_PROP_BIT_WRITE;
static const uint8_t char_prop_read_write_notify = ESP_GATT_CHAR_PROP_BIT_WRITE | ESP_GATT_CHAR_PROP_BIT_READ | ESP_GATT_CHAR_PROP_BIT_NOTIFY;
static const uint8_t heart_measurement_ccc[2] = {0x00, 0x00};
static const uint8_t char_value[4] = {0x11, 0x22, 0x33, 0x44};
#define CHAR_DECLARATION_SIZE (sizeof(uint8_t))
#define SVC_INST_ID 0
/* Full Database Description - Used to add attributes into the database */
static const esp_gatts_attr_db_t gatt_db[HRS_IDX_NB] =
{
// Service Declaration
[IDX_SVC] =
{{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&primary_service_uuid, ESP_GATT_PERM_READ,
sizeof(uint16_t), sizeof(GATTS_SERVICE_UUID_TEST), (uint8_t *)&GATTS_SERVICE_UUID_TEST}},
/* Characteristic Declaration */
[IDX_CHAR_A] =
{{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&character_declaration_uuid, ESP_GATT_PERM_READ,
CHAR_DECLARATION_SIZE, CHAR_DECLARATION_SIZE, (uint8_t *)&char_prop_read_write_notify}},
/* Characteristic Value */
[IDX_CHAR_VAL_A] =
{{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&GATTS_CHAR_UUID_TEST_A, ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE,
GATTS_DEMO_CHAR_VAL_LEN_MAX, sizeof(char_value), (uint8_t *)char_value}},
/* Client Characteristic Configuration Descriptor */
[IDX_CHAR_CFG_A] =
{{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&character_client_config_uuid, ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE,
sizeof(uint16_t), sizeof(heart_measurement_ccc), (uint8_t *)heart_measurement_ccc}},
/* Characteristic Declaration */
[IDX_CHAR_B] =
{{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&character_declaration_uuid, ESP_GATT_PERM_READ,
CHAR_DECLARATION_SIZE, CHAR_DECLARATION_SIZE, (uint8_t *)&char_prop_read}},
/* Characteristic Value */
[IDX_CHAR_VAL_B] =
{{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&GATTS_CHAR_UUID_TEST_B, ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE,
GATTS_DEMO_CHAR_VAL_LEN_MAX, sizeof(char_value), (uint8_t *)char_value}},
/* Characteristic Declaration */
[IDX_CHAR_C] =
{{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&character_declaration_uuid, ESP_GATT_PERM_READ,
CHAR_DECLARATION_SIZE, CHAR_DECLARATION_SIZE, (uint8_t *)&char_prop_write}},
/* Characteristic Value */
[IDX_CHAR_VAL_C] =
{{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&GATTS_CHAR_UUID_TEST_C, ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE,
GATTS_DEMO_CHAR_VAL_LEN_MAX, sizeof(char_value), (uint8_t *)char_value}},
};
static char *esp_key_type_to_str(esp_ble_key_type_t key_type)
{
char *key_str = NULL;
switch(key_type) {
case ESP_LE_KEY_NONE:
key_str = "ESP_LE_KEY_NONE";
break;
case ESP_LE_KEY_PENC:
key_str = "ESP_LE_KEY_PENC";
break;
case ESP_LE_KEY_PID:
key_str = "ESP_LE_KEY_PID";
break;
case ESP_LE_KEY_PCSRK:
key_str = "ESP_LE_KEY_PCSRK";
break;
case ESP_LE_KEY_PLK:
key_str = "ESP_LE_KEY_PLK";
break;
case ESP_LE_KEY_LLK:
key_str = "ESP_LE_KEY_LLK";
break;
case ESP_LE_KEY_LENC:
key_str = "ESP_LE_KEY_LENC";
break;
case ESP_LE_KEY_LID:
key_str = "ESP_LE_KEY_LID";
break;
case ESP_LE_KEY_LCSRK:
key_str = "ESP_LE_KEY_LCSRK";
break;
default:
key_str = "INVALID BLE KEY TYPE";
break;
}
return key_str;
}
static char *esp_auth_req_to_str(esp_ble_auth_req_t auth_req)
{
char *auth_str = NULL;
switch(auth_req) {
case ESP_LE_AUTH_NO_BOND:
auth_str = "ESP_LE_AUTH_NO_BOND";
break;
case ESP_LE_AUTH_BOND:
auth_str = "ESP_LE_AUTH_BOND";
break;
case ESP_LE_AUTH_REQ_MITM:
auth_str = "ESP_LE_AUTH_REQ_MITM";
break;
case ESP_LE_AUTH_REQ_BOND_MITM:
auth_str = "ESP_LE_AUTH_REQ_BOND_MITM";
break;
case ESP_LE_AUTH_REQ_SC_ONLY:
auth_str = "ESP_LE_AUTH_REQ_SC_ONLY";
break;
case ESP_LE_AUTH_REQ_SC_BOND:
auth_str = "ESP_LE_AUTH_REQ_SC_BOND";
break;
case ESP_LE_AUTH_REQ_SC_MITM:
auth_str = "ESP_LE_AUTH_REQ_SC_MITM";
break;
case ESP_LE_AUTH_REQ_SC_MITM_BOND:
auth_str = "ESP_LE_AUTH_REQ_SC_MITM_BOND";
break;
default:
auth_str = "INVALID BLE AUTH REQ";
break;
}
return auth_str;
}
static void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param)
{
switch (event) {
case ESP_GAP_BLE_EXT_ADV_SET_PARAMS_COMPLETE_EVT:
ESP_LOGI(LOG_TAG,"Extended advertising params set, status %d", param->ext_adv_set_params.status);
esp_ble_gap_config_ext_adv_data_raw(EXT_ADV_HANDLE, sizeof(ext_adv_raw_data), &ext_adv_raw_data[0]);
break;
case ESP_GAP_BLE_EXT_ADV_DATA_SET_COMPLETE_EVT:
ESP_LOGI(LOG_TAG,"Extended advertising data set, status %d", param->ext_adv_data_set.status);
esp_ble_gap_ext_adv_start(NUM_EXT_ADV_SET, &ext_adv[0]);
break;
case ESP_GAP_BLE_EXT_ADV_START_COMPLETE_EVT:
ESP_LOGI(LOG_TAG, "Extended advertising start, status %d", param->ext_adv_data_set.status);
break;
case ESP_GAP_BLE_ADV_TERMINATED_EVT:
ESP_LOGI(LOG_TAG, "Extended advertising terminated, status %d", param->adv_terminate.status);
if(param->adv_terminate.status == 0x00) {
ESP_LOGI(LOG_TAG, "Advertising successfully ended with a connection being created");
}
break;
case ESP_GAP_BLE_PASSKEY_REQ_EVT: /* passkey request event */
/* Call the following function to input the passkey which is displayed on the remote device */
ESP_LOGI(LOG_TAG, "Passkey request");
//esp_ble_passkey_reply(heart_rate_profile_tab[HEART_PROFILE_APP_IDX].remote_bda, true, 0x00);
break;
case ESP_GAP_BLE_OOB_REQ_EVT: {
ESP_LOGI(LOG_TAG, "OOB request");
uint8_t tk[16] = {1}; //If you paired with OOB, both devices need to use the same tk
esp_ble_oob_req_reply(param->ble_security.ble_req.bd_addr, tk, sizeof(tk));
break;
}
case ESP_GAP_BLE_LOCAL_IR_EVT: /* BLE local IR event */
ESP_LOGI(LOG_TAG, "Local identity root");
break;
case ESP_GAP_BLE_LOCAL_ER_EVT: /* BLE local ER event */
ESP_LOGI(LOG_TAG, "Local encryption root");
break;
case ESP_GAP_BLE_NC_REQ_EVT:
/* The app will receive this evt when the IO has DisplayYesNO capability and the peer device IO also has DisplayYesNo capability.
show the passkey number to the user to confirm it with the number displayed by peer device. */
esp_ble_confirm_reply(param->ble_security.ble_req.bd_addr, true);
ESP_LOGI(LOG_TAG, "Numeric Comparison request, passkey %" PRIu32, param->ble_security.key_notif.passkey);
break;
case ESP_GAP_BLE_SEC_REQ_EVT:
/* send the positive(true) security response to the peer device to accept the security request.
If not accept the security request, should send the security response with negative(false) accept value*/
esp_ble_gap_security_rsp(param->ble_security.ble_req.bd_addr, true);
break;
case ESP_GAP_BLE_PASSKEY_NOTIF_EVT: ///the app will receive this evt when the IO has Output capability and the peer device IO has Input capability.
///show the passkey number to the user to input it in the peer device.
ESP_LOGI(LOG_TAG, "Passkey notify, passkey %06" PRIu32, param->ble_security.key_notif.passkey);
break;
case ESP_GAP_BLE_KEY_EVT:
//shows the ble key info share with peer device to the user.
ESP_LOGI(LOG_TAG, "Key exchanged, key_type %s", esp_key_type_to_str(param->ble_security.ble_key.key_type));
if (param->ble_security.ble_key.key_type == ESP_LE_KEY_PID) {
ESP_LOGI(LOG_TAG, "peer addr "ESP_BD_ADDR_STR"", ESP_BD_ADDR_HEX(param->ble_security.ble_key.bd_addr));
}
break;
case ESP_GAP_BLE_AUTH_CMPL_EVT: {
esp_bd_addr_t bd_addr;
memcpy(bd_addr, param->ble_security.auth_cmpl.bd_addr, sizeof(esp_bd_addr_t));
ESP_LOGI(LOG_TAG, "Authentication complete, addr_type %u, addr "ESP_BD_ADDR_STR"",
param->ble_security.auth_cmpl.addr_type, ESP_BD_ADDR_HEX(bd_addr));
if(!param->ble_security.auth_cmpl.success) {
ESP_LOGI(LOG_TAG, "Pairing failed, reason 0x%x",param->ble_security.auth_cmpl.fail_reason);
} else {
ESP_LOGI(LOG_TAG, "Pairing successfully, auth_mode %s",esp_auth_req_to_str(param->ble_security.auth_cmpl.auth_mode));
// Setting CTE connection transmit parameters
cte_conn_trans_params.conn_handle = cur_conn_hdl;
ESP_LOGI(LOG_TAG, "Set CTE connection transmit params, conn_handle %d", cur_conn_hdl);
esp_ble_cte_set_connection_transmit_params(&cte_conn_trans_params);
}
break;
}
case ESP_GAP_BLE_REMOVE_BOND_DEV_COMPLETE_EVT: {
ESP_LOGD(LOG_TAG, "Bond device remove, status %d, device "ESP_BD_ADDR_STR"",
param->remove_bond_dev_cmpl.status, ESP_BD_ADDR_HEX(param->remove_bond_dev_cmpl.bd_addr));
break;
}
case ESP_GAP_BLE_SET_LOCAL_PRIVACY_COMPLETE_EVT:
ESP_LOGI(LOG_TAG, "Local privacy config, status %x", param->local_privacy_cmpl.status);
esp_ble_gap_ext_adv_set_params(EXT_ADV_HANDLE, &ext_adv_params_2M);
break;
case ESP_GAP_BLE_UPDATE_CONN_PARAMS_EVT:
ESP_LOGI(LOG_TAG, "Connection params update, status %d, conn_int %d, latency %d, timeout %d",
param->update_conn_params.status,
param->update_conn_params.conn_int,
param->update_conn_params.latency,
param->update_conn_params.timeout);
break;
default:
break;
}
}
static void gatts_profile_event_handler(esp_gatts_cb_event_t event,
esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param)
{
switch (event) {
case ESP_GATTS_REG_EVT:
ESP_LOGI(LOG_TAG, "GATT server register, status %d", param->reg.status);
//generate a resolvable random address
esp_ble_gap_config_local_privacy(true);
esp_ble_gatts_create_attr_tab(gatt_db, gatts_if, HRS_IDX_NB, SVC_INST_ID);
break;
case ESP_GATTS_READ_EVT:
ESP_LOGI(LOG_TAG, "Characteristic read");
break;
case ESP_GATTS_WRITE_EVT:
ESP_LOGI(LOG_TAG, "Characteristic write, value ");
ESP_LOG_BUFFER_HEX(LOG_TAG, param->write.value, param->write.len);
break;
case ESP_GATTS_EXEC_WRITE_EVT:
break;
case ESP_GATTS_MTU_EVT:
break;
case ESP_GATTS_CONF_EVT:
break;
case ESP_GATTS_UNREG_EVT:
break;
case ESP_GATTS_DELETE_EVT:
break;
case ESP_GATTS_START_EVT:
break;
case ESP_GATTS_STOP_EVT:
break;
case ESP_GATTS_CONNECT_EVT:
ESP_LOGI(LOG_TAG, "Connected, conn_id %u, remote "ESP_BD_ADDR_STR"",
param->connect.conn_id, ESP_BD_ADDR_HEX(param->connect.remote_bda));
// Save conn_handle for CTE
cur_conn_hdl = param->connect.conn_handle;
/* start security connect with peer device when receive the connect event sent by the master */
esp_ble_set_encryption(param->connect.remote_bda, ESP_BLE_SEC_ENCRYPT_MITM);
break;
case ESP_GATTS_DISCONNECT_EVT:
ESP_LOGI(LOG_TAG, "Disconnected, remote "ESP_BD_ADDR_STR", reason 0x%x",
ESP_BD_ADDR_HEX(param->disconnect.remote_bda), param->disconnect.reason);
/* start advertising again when missing the connect */
esp_ble_gap_ext_adv_start(NUM_EXT_ADV_SET, &ext_adv[0]);
break;
case ESP_GATTS_OPEN_EVT:
break;
case ESP_GATTS_CANCEL_OPEN_EVT:
break;
case ESP_GATTS_CLOSE_EVT:
break;
case ESP_GATTS_LISTEN_EVT:
break;
case ESP_GATTS_CONGEST_EVT:
break;
case ESP_GATTS_CREAT_ATTR_TAB_EVT: {
if (param->create.status == ESP_GATT_OK){
if(param->add_attr_tab.num_handle == HRS_IDX_NB) {
ESP_LOGI(LOG_TAG, "Attribute table create successfully, num_handle %x", param->add_attr_tab.num_handle);
memcpy(profile_handle_table, param->add_attr_tab.handles,
sizeof(profile_handle_table));
esp_ble_gatts_start_service(profile_handle_table[IDX_SVC]);
}else{
ESP_LOGE(LOG_TAG, "Attribute table create abnormally, num_handle (%d) doesn't equal to HRS_IDX_NB(%d)",
param->add_attr_tab.num_handle, HRS_IDX_NB);
}
}else{
ESP_LOGE(LOG_TAG, "Attribute table create failed, status %x", param->create.status);
}
break;
}
default:
break;
}
}
static void cte_event_handler(esp_ble_cte_cb_event_t event, esp_ble_cte_cb_param_t *param)
{
switch (event) {
case ESP_BLE_CTE_SET_CONN_TRANS_PARAMS_CMPL_EVT:
ESP_LOGI(LOG_TAG, "CTE set connection transmit params, status %d", param->conn_trans_params_cmpl.status);
if (!param->conn_trans_params_cmpl.status) {
ESP_LOGI(LOG_TAG, "Setting CTE connection response enable");
// Enable CTE response for connection
cte_conn_rsp_en.conn_handle = cur_conn_hdl;
esp_ble_cte_connection_cte_response_enable(&cte_conn_rsp_en);
}
break;
case ESP_BLE_CTE_SET_CONN_RECV_PARAMS_CMPL_EVT:
ESP_LOGI(LOG_TAG, "CTE set connection receive params, status %d", param->conn_recv_params_cmpl.status);
break;
case ESP_BLE_CTE_SET_CONN_REQ_ENABLE_CMPL_EVT:
ESP_LOGI(LOG_TAG, "CTE set connection request enable, status %d", param->conn_req_en_cmpl.status);
break;
case ESP_BLE_CTE_SET_CONN_RSP_ENABLE_CMPL_EVT:
ESP_LOGI(LOG_TAG, "CTE set connection response enable, status %d", param->conn_rsp_en_cmpl.status);
break;
case ESP_BLE_CTE_READ_ANT_INFOR_CMPL_EVT:
break;
case ESP_BLE_CTE_CONN_IQ_REPORT_EVT:
break;
case ESP_BLE_CTE_REQUEST_FAILED_EVT:
break;
default:
break;
}
}
static void gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if,
esp_ble_gatts_cb_param_t *param)
{
/* If event is register event, store the gatts_if for each profile */
if (event == ESP_GATTS_REG_EVT) {
if (param->reg.status == ESP_GATT_OK) {
heart_rate_profile_tab[HEART_PROFILE_APP_IDX].gatts_if = gatts_if;
} else {
ESP_LOGI(LOG_TAG, "Reg app failed, app_id %04x, status %d",
param->reg.app_id,
param->reg.status);
return;
}
}
do {
int idx;
for (idx = 0; idx < HEART_PROFILE_NUM; idx++) {
if (gatts_if == ESP_GATT_IF_NONE || /* ESP_GATT_IF_NONE, not specify a certain gatt_if, need to call every profile cb function */
gatts_if == heart_rate_profile_tab[idx].gatts_if) {
if (heart_rate_profile_tab[idx].gatts_cb) {
heart_rate_profile_tab[idx].gatts_cb(event, gatts_if, param);
}
}
}
} while (0);
}
void app_main(void)
{
esp_err_t ret;
// Initialize NVS.
ret = nvs_flash_init();
if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
ESP_ERROR_CHECK(nvs_flash_erase());
ret = nvs_flash_init();
}
ESP_ERROR_CHECK( ret );
ESP_ERROR_CHECK(esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT));
esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT();
ret = esp_bt_controller_init(&bt_cfg);
if (ret) {
ESP_LOGE(LOG_TAG, "%s init controller failed: %s", __func__, esp_err_to_name(ret));
return;
}
ret = esp_bt_controller_enable(ESP_BT_MODE_BLE);
if (ret) {
ESP_LOGE(LOG_TAG, "%s enable controller failed: %s", __func__, esp_err_to_name(ret));
return;
}
ESP_LOGI(LOG_TAG, "%s init bluetooth", __func__);
esp_bluedroid_config_t cfg = BT_BLUEDROID_INIT_CONFIG_DEFAULT();
ret = esp_bluedroid_init_with_cfg(&cfg);
if (ret) {
ESP_LOGE(LOG_TAG, "%s init bluetooth failed: %s", __func__, esp_err_to_name(ret));
return;
}
ret = esp_bluedroid_enable();
if (ret) {
ESP_LOGE(LOG_TAG, "%s enable bluetooth failed: %s", __func__, esp_err_to_name(ret));
return;
}
ret = esp_ble_cte_register_callback(cte_event_handler);
if (ret){
ESP_LOGE(LOG_TAG, "CTE callback register error, error code = %x", ret);
return;
}
ret = esp_ble_gatts_register_callback(gatts_event_handler);
if (ret){
ESP_LOGE(LOG_TAG, "gatts register error, error code = %x", ret);
return;
}
ret = esp_ble_gap_register_callback(gap_event_handler);
if (ret){
ESP_LOGE(LOG_TAG, "gap register error, error code = %x", ret);
return;
}
ret = esp_ble_gatts_app_register(ESP_HEART_RATE_APP_ID);
if (ret){
ESP_LOGE(LOG_TAG, "gatts app register error, error code = %x", ret);
return;
}
/* set the security iocap & auth_req & key size & init key response key parameters to the stack*/
esp_ble_auth_req_t auth_req = ESP_LE_AUTH_REQ_SC_MITM_BOND; //bonding with peer device after authentication
esp_ble_io_cap_t iocap = ESP_IO_CAP_NONE; //set the IO capability to No output No input
uint8_t key_size = 16; //the key size should be 7~16 bytes
uint8_t init_key = ESP_BLE_ENC_KEY_MASK | ESP_BLE_ID_KEY_MASK;
uint8_t rsp_key = ESP_BLE_ENC_KEY_MASK | ESP_BLE_ID_KEY_MASK;
//set static passkey
uint32_t passkey = 123456;
uint8_t auth_option = ESP_BLE_ONLY_ACCEPT_SPECIFIED_AUTH_DISABLE;
uint8_t oob_support = ESP_BLE_OOB_DISABLE;
esp_ble_gap_set_security_param(ESP_BLE_SM_SET_STATIC_PASSKEY, &passkey, sizeof(uint32_t));
esp_ble_gap_set_security_param(ESP_BLE_SM_AUTHEN_REQ_MODE, &auth_req, sizeof(uint8_t));
esp_ble_gap_set_security_param(ESP_BLE_SM_IOCAP_MODE, &iocap, sizeof(uint8_t));
esp_ble_gap_set_security_param(ESP_BLE_SM_MAX_KEY_SIZE, &key_size, sizeof(uint8_t));
esp_ble_gap_set_security_param(ESP_BLE_SM_ONLY_ACCEPT_SPECIFIED_SEC_AUTH, &auth_option, sizeof(uint8_t));
esp_ble_gap_set_security_param(ESP_BLE_SM_OOB_SUPPORT, &oob_support, sizeof(uint8_t));
/* If your BLE device acts as a Slave, the init_key means you hope which types of key of the master should distribute to you,
and the response key means which key you can distribute to the master;
If your BLE device acts as a master, the response key means you hope which types of key of the slave should distribute to you,
and the init key means which key you can distribute to the slave. */
esp_ble_gap_set_security_param(ESP_BLE_SM_SET_INIT_KEY, &init_key, sizeof(uint8_t));
esp_ble_gap_set_security_param(ESP_BLE_SM_SET_RSP_KEY, &rsp_key, sizeof(uint8_t));
}

View File

@@ -0,0 +1,28 @@
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/* Attributes State Machine */
enum
{
IDX_SVC,
IDX_CHAR_A,
IDX_CHAR_VAL_A,
IDX_CHAR_CFG_A,
IDX_CHAR_B,
IDX_CHAR_VAL_B,
IDX_CHAR_C,
IDX_CHAR_VAL_C,
HRS_IDX_NB,
};

View File

@@ -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

View File

@@ -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})

View File

@@ -0,0 +1,88 @@
| Supported Targets | ESP32-C5 | ESP32-C6 | ESP32-C61 | ESP32-H2 |
| ----------------- | -------- | -------- | --------- | -------- |
# BLE Periodic Advertiser With Response (PAwR) Advertiser Example
(See the README.md file in the upper level 'examples' directory for more information about examples.)
This example demonstrates **Periodic Advertising with Responses (PAwR)** on the advertiser side.
This example starts PAwR advertising with configurable subevents and response slots.
It uses Bluetooth controller and Bluedroid stack based BLE host.
This example aims at understanding PAwR advertisement and related Bluedroid APIs.
## How to Test
### Pairing Test: Advertiser + Synchronizer
This example should be paired with [ble_pawr_synchronizer](../ble_pawr_synchronizer) for testing.
**Test Setup:**
1. **Device A (Advertiser)**: Flash `ble_pawr_advertiser` example
2. **Device B (Synchronizer)**: Flash `ble_pawr_synchronizer` example
**Steps:**
1. Power on Device A first (advertiser starts broadcasting)
2. Power on Device B (synchronizer starts scanning)
3. Device B will automatically detect and sync to Device A
4. Both devices will exchange PAwR data
**Expected Behavior:**
- Advertiser sends periodic advertising with subevents
- Synchronizer receives subevents and sends responses
- Advertiser receives response reports
## How to Use Example
Before project configuration and build, be sure to set the correct chip target using:
```bash
idf.py set-target <chip_name>
```
### Configure the project
Open the project configuration menu:
```bash
idf.py menuconfig
```
In the `Example Configuration` menu:
* Select I/O capabilities of device from `Example Configuration --> I/O Capability`, default is `Just_works`.
### Build and Flash
Run `idf.py -p PORT flash monitor` to build, flash and monitor the project.
(To exit the serial monitor, type ``Ctrl-]``.)
See the [Getting Started Guide](https://idf.espressif.com/) for full steps to configure and use ESP-IDF to build projects.
## Example Output
```
I (504) BLE_PAWR_ADV: Device Address: 40:4c:ca:46:1f:e2
I (514) BLE_PAWR_ADV: Extended advertising params set, status 0, instance 0
I (524) BLE_PAWR_ADV: Extended advertising data set, status 0, instance 0
I (534) BLE_PAWR_ADV: Extended advertising start, status 0, instance num 1
I (544) BLE_PAWR_ADV: Periodic advertising params set, status 0, instance 0
I (554) BLE_PAWR_ADV: Periodic advertising start, status 0, instance 0
I (564) BLE_PAWR_ADV: PAwR advertising started successfully
I (574) BLE_PAWR_ADV: BLE PAwR Advertiser example started
I (664) BLE_PAWR_ADV: [Request] data: 0x00, subevt start:0, subevt count:5
I (814) BLE_PAWR_ADV: [Request] data: 0x05, subevt start:5, subevt count:2
I (964) BLE_PAWR_ADV: [Request] data: 0x07, subevt start:7, subevt count:2
I (1114) BLE_PAWR_ADV: [Request] data: 0x09, subevt start:9, subevt count:2
I (1163054) BLE_PAWR_ADV: [Response] adv_handle:0, subevent:0, tx_status:0, num_rsp:1
I (1163054) BLE_PAWR_ADV: Response slot:114, data_length:1
I (1163054) BLE_PAWR_ADV: data: 0x8c, 0x0
```
## Troubleshooting
For any technical queries, please open an [issue](https://github.com/espressif/esp-idf/issues) on GitHub. We will get back to you soon.

View File

@@ -0,0 +1,3 @@
idf_component_register(SRCS "main.c"
PRIV_REQUIRES bt nvs_flash
INCLUDE_DIRS ".")

View File

@@ -0,0 +1,307 @@
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
/****************************************************************************
*
* This demo showcases BLE Periodic Advertising with Responses (PAwR) on the advertiser side.
* The device starts extended advertising and periodic advertising with subevents.
* When the host receives ESP_GAP_BLE_PERIODIC_ADV_SUBEVT_DATA_REQUEST_EVT, it provides
* per-subevent data via esp_ble_gap_set_periodic_adv_subevent_data().
* If a synchronizer sends responses, the advertiser will receive
* ESP_GAP_BLE_PERIODIC_ADV_RESPONSE_REPORT_EVT.
*
****************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "esp_system.h"
#include "esp_log.h"
#include "nvs_flash.h"
#include "esp_bt.h"
#include "esp_bt_main.h"
#include "esp_gap_ble_api.h"
#include "esp_bt_defs.h"
#include "sdkconfig.h"
#define TAG "BLE_PAWR_ADV"
#define EXT_ADV_HANDLE 0
#define NUM_EXT_ADV 1
#define SEM_WAIT_TIMEOUT_MS 5000
#define FUNC_SEND_WAIT_SEM(func, sem) do {\
esp_err_t __err_rc = (func);\
if (__err_rc != ESP_OK) { \
ESP_LOGE(TAG, "%s failed, error = %d", #func, __err_rc); \
return; \
} \
xSemaphoreTake(sem, portMAX_DELAY); \
} while(0);
// PAwR parameters
#define BLE_PAWR_SUB_DATA_LEN (20)
static SemaphoreHandle_t test_sem = NULL;
static esp_ble_subevent_params sub_data_params[10];
static uint8_t sub_data_pattern[BLE_PAWR_SUB_DATA_LEN] = {0};
static uint8_t subevent_data_buffers[10][BLE_PAWR_SUB_DATA_LEN];
// Extended advertising data
static uint8_t raw_ext_adv_data[] = {
0x02, ESP_BLE_AD_TYPE_FLAG, 0x06,
0x02, ESP_BLE_AD_TYPE_TX_PWR, 0xeb,
0x10, ESP_BLE_AD_TYPE_NAME_CMPL, 'E', 'S', 'P', '_', 'E', 'X', 'T', 'P', 'A', 'w', 'R', '_', 'A', 'D', 'V'
};
static esp_ble_gap_ext_adv_params_t ext_adv_params = {
.type = ESP_BLE_GAP_SET_EXT_ADV_PROP_NONCONN_NONSCANNABLE_UNDIRECTED,
.interval_min = 600,
.interval_max = 600,
.channel_map = ADV_CHNL_ALL,
.filter_policy = ADV_FILTER_ALLOW_SCAN_ANY_CON_ANY,
.primary_phy = ESP_BLE_GAP_PHY_1M,
.max_skip = 0,
.secondary_phy = ESP_BLE_GAP_PHY_2M,
.sid = 0,
.scan_req_notif = false,
.own_addr_type = BLE_ADDR_TYPE_RANDOM,
.tx_power = EXT_ADV_TX_PWR_NO_PREFERENCE,
};
static esp_ble_gap_periodic_adv_params_t periodic_adv_params = {
.interval_min = 600,
.interval_max = 600,
.properties = 0, // Do not include TX power
.num_subevents = 10,
.subevent_interval = 44,
.rsp_slot_delay = 20,
.rsp_slot_spacing = 32,
.num_rsp_slots = 5,
};
static esp_ble_gap_ext_adv_t ext_adv[1] = {
[0] = {EXT_ADV_HANDLE, 0, 0},
};
/**
* @brief GAP event handler
*/
static void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param)
{
if (param == NULL) {
ESP_LOGE(TAG, "GAP event handler received NULL param");
return;
}
switch (event) {
case ESP_GAP_BLE_EXT_ADV_SET_PARAMS_COMPLETE_EVT:
xSemaphoreGive(test_sem);
ESP_LOGI(TAG, "Extended advertising params set, status %d, instance %d",
param->ext_adv_set_params.status, param->ext_adv_set_params.instance);
break;
case ESP_GAP_BLE_EXT_ADV_DATA_SET_COMPLETE_EVT:
xSemaphoreGive(test_sem);
ESP_LOGI(TAG, "Extended advertising data set, status %d, instance %d",
param->ext_adv_data_set.status, param->ext_adv_data_set.instance);
break;
case ESP_GAP_BLE_EXT_ADV_START_COMPLETE_EVT:
xSemaphoreGive(test_sem);
ESP_LOGI(TAG, "Extended advertising start, status %d, instance num %d",
param->ext_adv_start.status, param->ext_adv_start.instance_num);
break;
case ESP_GAP_BLE_EXT_ADV_SET_RAND_ADDR_COMPLETE_EVT:
xSemaphoreGive(test_sem);
ESP_LOGI(TAG, "Extended advertising set random address, status %d, instance %d",
param->ext_adv_set_rand_addr.status, param->ext_adv_set_rand_addr.instance);
break;
case ESP_GAP_BLE_PERIODIC_ADV_SET_PARAMS_COMPLETE_EVT:
xSemaphoreGive(test_sem);
ESP_LOGI(TAG, "Periodic advertising params set, status %d, instance %d",
param->peroid_adv_set_params.status, param->peroid_adv_set_params.instance);
break;
case ESP_GAP_BLE_PERIODIC_ADV_DATA_SET_COMPLETE_EVT:
xSemaphoreGive(test_sem);
ESP_LOGI(TAG, "Periodic advertising data set, status %d, instance %d",
param->period_adv_data_set.status, param->period_adv_data_set.instance);
break;
case ESP_GAP_BLE_PERIODIC_ADV_START_COMPLETE_EVT:
xSemaphoreGive(test_sem);
ESP_LOGI(TAG, "Periodic advertising start, status %d, instance %d",
param->period_adv_start.status, param->period_adv_start.instance);
break;
case ESP_GAP_BLE_SET_PERIODIC_ADV_SUBEVT_DATA_EVT:
ESP_LOGI(TAG, "Periodic adv subevent data set, status %d, adv_handle %d",
param->pa_subevt_data_evt.status, param->pa_subevt_data_evt.adv_handle);
break;
case ESP_GAP_BLE_PERIODIC_ADV_SUBEVT_DATA_REQUEST_EVT: {
ESP_LOGI(TAG, "[Request] data: 0x%02x, subevt start:%d, subevt count:%d",
sub_data_pattern[0],
param->pa_subevt_data_req_evt.subevt_start,
param->pa_subevt_data_req_evt.subevt_data_count);
uint8_t sent_num = param->pa_subevt_data_req_evt.subevt_data_count;
if (sent_num > 10) {
ESP_LOGE(TAG, "Invalid subevent count: %d", sent_num);
sent_num = 10;
}
// Prepare subevent data
for (uint8_t i = 0; i < sent_num && i < 10; i++) {
uint8_t sub = (i + param->pa_subevt_data_req_evt.subevt_start) % 10;
// Prepare data pattern with overflow protection
sub_data_pattern[0] = (sub_data_pattern[0] + 1) % 256;
memset(&sub_data_pattern[1], sub, BLE_PAWR_SUB_DATA_LEN - 1);
// Copy to buffer
memcpy(subevent_data_buffers[i], sub_data_pattern, BLE_PAWR_SUB_DATA_LEN);
// Set subevent parameters
sub_data_params[i].subevent = sub;
sub_data_params[i].response_slot_start = 0;
sub_data_params[i].response_slot_count = 5;
sub_data_params[i].subevent_data_len = BLE_PAWR_SUB_DATA_LEN;
sub_data_params[i].subevent_data = subevent_data_buffers[i];
}
// Set subevent data
esp_ble_per_adv_subevent_data_params subevent_data_params = {0};
subevent_data_params.adv_handle = param->pa_subevt_data_req_evt.adv_handle;
subevent_data_params.num_subevents_with_data = sent_num;
subevent_data_params.subevent_params = sub_data_params;
esp_err_t ret = esp_ble_gap_set_periodic_adv_subevent_data(&subevent_data_params);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Failed to set Subevent Data, error = 0x%x", ret);
}
break;
}
case ESP_GAP_BLE_PERIODIC_ADV_RESPONSE_REPORT_EVT: {
ESP_LOGI(TAG, "[Response] adv_handle:%d, subevent:%d, tx_status:%d, num_rsp:%d",
param->pa_rsp_rpt_evt.adv_handle,
param->pa_rsp_rpt_evt.subevt,
param->pa_rsp_rpt_evt.tx_status,
param->pa_rsp_rpt_evt.num_rsp);
if (param->pa_rsp_rpt_evt.pa_rsp_info && param->pa_rsp_rpt_evt.num_rsp > 0) {
// Limit num_rsp to prevent array overflow
uint8_t num_rsp = (param->pa_rsp_rpt_evt.num_rsp > 10) ? 10 : param->pa_rsp_rpt_evt.num_rsp;
for (uint8_t i = 0; i < num_rsp && i < 10; i++) {
esp_ble_pa_rsp_info *rsp_info = &param->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");
}

View File

@@ -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

View File

@@ -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})

View File

@@ -0,0 +1,127 @@
| Supported Targets | ESP32-C5 | ESP32-C6 | ESP32-C61 | ESP32-H2 |
| ----------------- | -------- | -------- | --------- | -------- |
# BLE Periodic Advertiser With Response (PAwR) Advertiser Connection Example
(See the README.md file in the upper level 'examples' directory for more information about examples.)
This example demonstrates PAwR connection functionality as PAwR advertiser.
It uses Bluetooth controller and Bluedroid stack based BLE host.
This example aims at understanding how to establish connections using PAwR synchronization and related Bluedroid APIs.
## How to Test
### Pairing Test: Advertiser with Connection + Synchronizer
This example should be paired with [ble_pawr_synchronizer](../ble_pawr_synchronizer) for testing.
**Test Setup:**
1. **Device A (Advertiser with Connection)**: Flash `ble_pawr_advertiser_conn` example
2. **Device B (Synchronizer)**: Flash `ble_pawr_synchronizer` example
**Steps:**
1. Build and flash advertiser example to Device A:
```bash
cd examples/bluetooth/bluedroid/ble_50/ble_pawr_advertiser_conn
idf.py set-target <chip_name>
idf.py -p <PORT_A> build flash monitor
```
2. Build and flash synchronizer example to Device B:
```bash
cd examples/bluetooth/bluedroid/ble_50/ble_pawr_synchronizer
idf.py set-target <chip_name>
idf.py -p <PORT_B> build flash monitor
```
3. **Power on Device A first** (advertiser starts broadcasting)
4. **Power on Device B** (synchronizer starts scanning and syncing)
5. Device B sends responses containing connection information
6. Device A receives responses and extracts peer address
7. Device A establishes connection using `esp_ble_gattc_aux_open_with_pawr_synced()`
**Expected Behavior:**
- Advertiser sends periodic advertising with subevents
- Synchronizer receives subevents and sends responses containing its BLE address
- Advertiser receives response reports with connection data
- Advertiser establishes BLE connection to synchronizer using `esp_ble_gattc_aux_open_with_pawr_synced()`
- Advertiser automatically discovers GATT services and characteristics
- Advertiser configures notify to receive data from synchronizer
**Response Data Format:**
The synchronizer sends response data with the following format:
- `data[0]` = 0x00
- `data[1]` = 0x02
- `data[2-7]` = Synchronizer's BLE address (6 bytes, high to low: addr[5], addr[4], addr[3], addr[2], addr[1], addr[0])
- `data[8+]` = Additional data (optional)
**GATT Service:**
The synchronizer provides a GATT service (UUID: 0x00FF) with:
- Characteristic UUID: 0xFF01
- Properties: Read, Write, Notify
- CCCD for notify configuration
## How to Use Example
Before project configuration and build, be sure to set the correct chip target using:
```bash
idf.py set-target <chip_name>
```
### Configure the project
Open the project configuration menu:
```bash
idf.py menuconfig
```
In the `Example Configuration` menu:
* Select I/O capabilities of device from `Example Configuration --> I/O Capability`, default is `Just_works`.
### Build and Flash
Run `idf.py -p PORT flash monitor` to build, flash and monitor the project.
(To exit the serial monitor, type ``Ctrl-]``.)
See the [Getting Started Guide](https://idf.espressif.com/) for full steps to configure and use ESP-IDF to build projects.
## Example Output
```
I (562) BLE_PAWR_ADV_CONN: Device Address: 40:4c:ca:46:1f:e2
I (572) BLE_PAWR_ADV_CONN: Extended advertising params set, status 0, instance 0
I (582) BLE_PAWR_ADV_CONN: Extended advertising data set, status 0, instance 0
I (592) BLE_PAWR_ADV_CONN: Extended advertising start, status 0, instance num 1
I (602) BLE_PAWR_ADV_CONN: Periodic advertising params set, status 0, instance 0
I (612) BLE_PAWR_ADV_CONN: Periodic advertising start, status 0, instance 0
I (622) BLE_PAWR_ADV_CONN: PAwR advertising started successfully
I (632) BLE_PAWR_ADV_CONN: BLE PAwR Advertiser Connection example started
I (642) BLE_PAWR_ADV_CONN: [Request] data: 0x00, subevt start:0, subevt count:5
I (1842) BLE_PAWR_ADV_CONN: [Request] data: 0x17, subevt start:3, subevt count:2
I (1872) BLE_PAWR_ADV_CONN: [Response] adv_handle:0, subevent:0, tx_status:0, num_rsp:1
I (1872) BLE_PAWR_ADV_CONN: Response slot:2, data_length:10
I (1872) BLE_PAWR_ADV_CONN: data: 0x00, 0x02, 0x5e, 0x02, 0xf6, 0xf9, 0x55, 0x60, 0x00, 0x00
I (1882) BLE_PAWR_ADV_CONN: Connection create sent, adv handle = 0, subevent = 0
I (2192) BLE_PAWR_ADV_CONN: [Connection established], conn_id = 0x0000, status = 0x00
I (2202) BLE_PAWR_ADV_CONN: Connection Information:
I (2202) BLE_PAWR_ADV_CONN: Connection ID: 0x0000
I (2212) BLE_PAWR_ADV_CONN: Remote Address: 60:55:f9:f6:02:5e
I (2222) BLE_PAWR_ADV_CONN: GATTC open successfully, conn_id = 0x0000, MTU 23
I (2232) BLE_PAWR_ADV_CONN: Service discover complete, conn_id 0
I (2242) BLE_PAWR_ADV_CONN: Service search result, conn_id = 0x0000, is_primary 1
I (2252) BLE_PAWR_ADV_CONN: PAwR Sync Service found
I (2262) BLE_PAWR_ADV_CONN: Characteristic found, handle = X, properties = 0xXX
I (2272) BLE_PAWR_ADV_CONN: Notification register successfully, handle = X
I (2282) BLE_PAWR_ADV_CONN: Descriptor write successfully, notify enabled
```
## Troubleshooting
For any technical queries, please open an [issue](https://github.com/espressif/esp-idf/issues) on GitHub. We will get back to you soon.

View File

@@ -0,0 +1,3 @@
idf_component_register(SRCS "main.c"
PRIV_REQUIRES bt nvs_flash
INCLUDE_DIRS ".")

View File

@@ -0,0 +1,690 @@
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
/****************************************************************************
*
* This demo showcases BLE Periodic Advertising with Responses (PAwR) on the advertiser side
* with connection capability. The device starts extended advertising and periodic advertising
* with subevents. When the host receives ESP_GAP_BLE_PERIODIC_ADV_SUBEVT_DATA_REQUEST_EVT,
* it provides per-subevent data via esp_ble_gap_set_periodic_adv_subevent_data().
* If a synchronizer sends responses containing connection information, the advertiser will
* receive ESP_GAP_BLE_PERIODIC_ADV_RESPONSE_REPORT_EVT and can establish a connection using
* esp_ble_gattc_aux_open_with_pawr_synced().
*
****************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "esp_system.h"
#include "esp_log.h"
#include "nvs_flash.h"
#include "esp_bt.h"
#include "esp_bt_main.h"
#include "esp_gap_ble_api.h"
#include "esp_gattc_api.h"
#include "esp_gatt_defs.h"
#include "esp_bt_defs.h"
#include "sdkconfig.h"
#define TAG "BLE_PAWR_ADV_CONN"
#define EXT_ADV_HANDLE 0
#define NUM_EXT_ADV 1
#define PROFILE_A_APP_ID 0
#define SEM_WAIT_TIMEOUT_MS 5000
/* Service UUID and Characteristic UUID (matching synchronizer) */
#define GATTS_SERVICE_UUID_PAWR_SYNC 0x00FF
#define GATTS_CHAR_UUID_PAWR_SYNC 0xFF01
#define INVALID_HANDLE 0xFFFF
#define FUNC_SEND_WAIT_SEM(func, sem) do {\
esp_err_t __err_rc = (func);\
if (__err_rc != ESP_OK) { \
ESP_LOGE(TAG, "%s failed, error = %d", #func, __err_rc); \
return; \
} \
if (xSemaphoreTake(sem, pdMS_TO_TICKS(SEM_WAIT_TIMEOUT_MS)) != pdTRUE) { \
ESP_LOGE(TAG, "%s, semaphore timeout", __func__); \
return; \
} \
} while(0);
// PAwR parameters
#define BLE_PAWR_SUB_DATA_LEN (20)
static SemaphoreHandle_t test_sem = NULL;
static esp_ble_subevent_params sub_data_params[10];
static uint8_t sub_data_pattern[BLE_PAWR_SUB_DATA_LEN] = {0};
static uint8_t subevent_data_buffers[10][BLE_PAWR_SUB_DATA_LEN];
static bool is_connected = false;
static uint16_t conn_id = 0xFFFF;
// Connection parameters
static esp_ble_conn_params_t conn_params = {
.interval_min = 0x20, // 40 * 1.25ms = 50ms
.interval_max = 0x20, // 40 * 1.25ms = 50ms
.latency = 0,
.supervision_timeout = 0x1F4, // 500 * 10ms = 5000ms
};
// Extended advertising data
static uint8_t raw_ext_adv_data[] = {
0x02, ESP_BLE_AD_TYPE_FLAG, 0x06,
0x02, ESP_BLE_AD_TYPE_TX_PWR, 0xeb,
0x10, ESP_BLE_AD_TYPE_NAME_CMPL, 'E', 'S', 'P', '_', 'E', 'X', 'T', 'P', 'A', 'w', 'R', '_', 'A', 'D', 'V'
};
static esp_ble_gap_ext_adv_params_t ext_adv_params = {
.type = ESP_BLE_GAP_SET_EXT_ADV_PROP_NONCONN_NONSCANNABLE_UNDIRECTED,
.interval_min = 600,
.interval_max = 600,
.channel_map = ADV_CHNL_ALL,
.filter_policy = ADV_FILTER_ALLOW_SCAN_ANY_CON_ANY,
.primary_phy = ESP_BLE_GAP_PHY_1M,
.max_skip = 0,
.secondary_phy = ESP_BLE_GAP_PHY_2M,
.sid = 0,
.scan_req_notif = false,
.own_addr_type = BLE_ADDR_TYPE_RANDOM,
.tx_power = EXT_ADV_TX_PWR_NO_PREFERENCE,
};
static esp_ble_gap_periodic_adv_params_t periodic_adv_params = {
.interval_min = 600,
.interval_max = 600,
.properties = 0, // Do not include TX power
.num_subevents = 10,
.subevent_interval = 44,
.rsp_slot_delay = 20,
.rsp_slot_spacing = 32,
.num_rsp_slots = 5,
};
static esp_ble_gap_ext_adv_t ext_adv[1] = {
[0] = {EXT_ADV_HANDLE, 0, 0},
};
// Forward declaration
static void gattc_profile_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param);
static struct gattc_profile_inst {
esp_gattc_cb_t gattc_cb;
uint16_t gattc_if;
uint16_t app_id;
uint16_t conn_id;
esp_bd_addr_t remote_bda;
uint16_t service_start_handle;
uint16_t service_end_handle;
uint16_t char_handle;
} gl_profile_tab[1] = {
[PROFILE_A_APP_ID] = {
.gattc_cb = gattc_profile_event_handler,
.gattc_if = ESP_GATT_IF_NONE,
.service_start_handle = INVALID_HANDLE,
.service_end_handle = INVALID_HANDLE,
.char_handle = INVALID_HANDLE,
},
};
/* UUID for service discovery */
static esp_bt_uuid_t remote_filter_service_uuid = {
.len = ESP_UUID_LEN_16,
.uuid = {.uuid16 = GATTS_SERVICE_UUID_PAWR_SYNC},
};
static esp_bt_uuid_t remote_filter_char_uuid = {
.len = ESP_UUID_LEN_16,
.uuid = {.uuid16 = GATTS_CHAR_UUID_PAWR_SYNC},
};
static esp_bt_uuid_t notify_descr_uuid = {
.len = ESP_UUID_LEN_16,
.uuid = {.uuid16 = ESP_GATT_UUID_CHAR_CLIENT_CONFIG},
};
static bool get_server = false;
/**
* @brief Print connection information
*/
static void print_conn_info(uint16_t conn_id, esp_bd_addr_t remote_bda)
{
ESP_LOGI(TAG, "Connection Information:");
ESP_LOGI(TAG, " Connection ID: 0x%04x", conn_id);
ESP_LOGI(TAG, " Remote Address: "ESP_BD_ADDR_STR"", ESP_BD_ADDR_HEX(remote_bda));
}
/**
* @brief GAP event handler
*/
static void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param)
{
if (param == NULL) {
ESP_LOGE(TAG, "GAP event handler received NULL param");
return;
}
switch (event) {
case ESP_GAP_BLE_EXT_ADV_SET_PARAMS_COMPLETE_EVT:
xSemaphoreGive(test_sem);
ESP_LOGI(TAG, "Extended advertising params set, status %d, instance %d",
param->ext_adv_set_params.status, param->ext_adv_set_params.instance);
break;
case ESP_GAP_BLE_EXT_ADV_DATA_SET_COMPLETE_EVT:
xSemaphoreGive(test_sem);
ESP_LOGI(TAG, "Extended advertising data set, status %d, instance %d",
param->ext_adv_data_set.status, param->ext_adv_data_set.instance);
break;
case ESP_GAP_BLE_EXT_ADV_START_COMPLETE_EVT:
xSemaphoreGive(test_sem);
ESP_LOGI(TAG, "Extended advertising start, status %d, instance num %d",
param->ext_adv_start.status, param->ext_adv_start.instance_num);
break;
case ESP_GAP_BLE_EXT_ADV_SET_RAND_ADDR_COMPLETE_EVT:
xSemaphoreGive(test_sem);
ESP_LOGI(TAG, "Extended advertising set random address complete, status %d, instance %d",
param->ext_adv_set_rand_addr.status, param->ext_adv_set_rand_addr.instance);
break;
case ESP_GAP_BLE_PERIODIC_ADV_SET_PARAMS_COMPLETE_EVT:
xSemaphoreGive(test_sem);
ESP_LOGI(TAG, "Periodic advertising params set, status %d, instance %d",
param->peroid_adv_set_params.status, param->peroid_adv_set_params.instance);
break;
case ESP_GAP_BLE_PERIODIC_ADV_START_COMPLETE_EVT:
xSemaphoreGive(test_sem);
ESP_LOGI(TAG, "Periodic advertising start, status %d, instance %d",
param->period_adv_start.status, param->period_adv_start.instance);
break;
case ESP_GAP_BLE_SET_PERIODIC_ADV_SUBEVT_DATA_EVT:
if (param->pa_subevt_data_evt.status != ESP_OK) {
if (param->pa_subevt_data_evt.status == 0x146)
{
ESP_LOGW(TAG, "Periodic adv subevent data set too late, status %d, adv_handle %d",
param->pa_subevt_data_evt.status, param->pa_subevt_data_evt.adv_handle);
} else {
ESP_LOGE(TAG, "Periodic adv subevent data set failed, status %d, adv_handle %d",
param->pa_subevt_data_evt.status, param->pa_subevt_data_evt.adv_handle);
}
break;
}
break;
case ESP_GAP_BLE_PERIODIC_ADV_SUBEVT_DATA_REQUEST_EVT: {
// ESP_LOGI(TAG, "[Request] data: 0x%02x, subevt start:%d, subevt count:%d",
// sub_data_pattern[0],
// param->pa_subevt_data_req_evt.subevt_start,
// param->pa_subevt_data_req_evt.subevt_data_count);
uint8_t sent_num = param->pa_subevt_data_req_evt.subevt_data_count;
if (sent_num > 10) {
ESP_LOGE(TAG, "Invalid subevent count: %d", sent_num);
sent_num = 10;
}
// Prepare subevent data
for (uint8_t i = 0; i < sent_num && i < 10; i++) {
uint8_t sub = (i + param->pa_subevt_data_req_evt.subevt_start) % 10;
// Prepare data pattern with overflow protection
sub_data_pattern[0] = (sub_data_pattern[0] + 1) % 256;
memset(&sub_data_pattern[1], sub, BLE_PAWR_SUB_DATA_LEN - 1);
// Copy to buffer
memcpy(subevent_data_buffers[i], sub_data_pattern, BLE_PAWR_SUB_DATA_LEN);
// Set subevent parameters
sub_data_params[i].subevent = sub;
sub_data_params[i].response_slot_start = 0;
sub_data_params[i].response_slot_count = 5;
sub_data_params[i].subevent_data_len = BLE_PAWR_SUB_DATA_LEN;
sub_data_params[i].subevent_data = subevent_data_buffers[i];
}
// Set subevent data
esp_ble_per_adv_subevent_data_params subevent_data_params = {0};
subevent_data_params.adv_handle = param->pa_subevt_data_req_evt.adv_handle;
subevent_data_params.num_subevents_with_data = sent_num;
subevent_data_params.subevent_params = sub_data_params;
esp_err_t ret = esp_ble_gap_set_periodic_adv_subevent_data(&subevent_data_params);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Failed to set Subevent Data, error = 0x%x", ret);
}
break;
}
case ESP_GAP_BLE_PERIODIC_ADV_RESPONSE_REPORT_EVT: {
// ESP_LOGI(TAG, "[Response] adv_handle:%d, subevent:%d, tx_status:%d, num_rsp:%d",
// param->pa_rsp_rpt_evt.adv_handle,
// param->pa_rsp_rpt_evt.subevt,
// param->pa_rsp_rpt_evt.tx_status,
// param->pa_rsp_rpt_evt.num_rsp);
if (param->pa_rsp_rpt_evt.pa_rsp_info && param->pa_rsp_rpt_evt.num_rsp > 0) {
// Limit num_rsp to prevent array overflow
uint8_t num_rsp = (param->pa_rsp_rpt_evt.num_rsp > 10) ? 10 : param->pa_rsp_rpt_evt.num_rsp;
for (uint8_t i = 0; i < num_rsp && i < 10; i++) {
esp_ble_pa_rsp_info *rsp_info = &param->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 *)&notify_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");
}

View File

@@ -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

View File

@@ -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)

View File

@@ -0,0 +1,61 @@
| Supported Targets | ESP32-C5 | ESP32-C6 | ESP32-C61 | ESP32-H2 |
| ----------------- | -------- | -------- | --------- | -------- |
# ESP-IDF PAwR Synchronizer Example
This example demonstrates **Periodic Advertising with Responses (PAwR)** on the synchronizer side.
- The device performs **extended scanning** and creates **periodic sync** to the advertiser.
- After sync is established, it selects a list of subevents to synchronize to via `esp_ble_gap_set_periodic_sync_subevent()`.
- For received subevents, it sends responses via `esp_ble_gap_set_periodic_adv_response_data()`.
- The response data includes the synchronizer's BLE address, allowing the advertiser to establish a connection.
- The device acts as a **GATTS server** with a service table, ready to accept connections from the advertiser.
## How to Use
### Pairing Test: Synchronizer + Advertiser
This example should be paired with [ble_pawr_advertiser](../ble_pawr_advertiser) or [ble_pawr_advertiser_conn](../ble_pawr_advertiser_conn) for testing.
**Test Setup:**
1. **Device A (Advertiser)**: Flash `ble_pawr_advertiser` or `ble_pawr_advertiser_conn` example
2. **Device B (Synchronizer)**: Flash this `ble_pawr_synchronizer` example
**Steps:**
1. Build and flash advertiser example to Device A:
```bash
cd examples/bluetooth/bluedroid/ble_50/ble_pawr_advertiser
idf.py set-target <chip_name>
idf.py -p <PORT_A> build flash monitor
```
2. Build and flash synchronizer example to Device B:
```bash
cd examples/bluetooth/bluedroid/ble_50/ble_pawr_synchronizer
idf.py set-target <chip_name>
idf.py -p <PORT_B> build flash monitor
```
3. **Power on Device A first** (advertiser starts broadcasting)
4. **Power on Device B** (synchronizer starts scanning)
5. Device B will automatically detect Device A by device name "ESP_EXTPAwR_ADV"
6. Device B creates periodic sync and selects subevents
7. Both devices exchange PAwR data
**Expected Log Hints:**
**On Synchronizer (Device B):**
- `Extended scanning start, status 0`
- `Local address cached: XX:XX:XX:XX:XX:XX, type = X`
- `Create sync with the peer device ESP_EXTPAwR_ADV`
- `Periodic advertising sync establish, status 0`
- `PA sync subevent, status 0`
- `create attribute table successfully, the number handle = X`
- `Service started, service_handle = X`
- `Set PA response data, status 0` (repeatedly when sending responses)
- `[Connection established], conn_id = 0xXXXX` (when advertiser connects)
**On Advertiser (Device A):**
- `PAwR advertising started successfully`
- `[Request] data: 0x00, subevt start:0, subevt count:5`
- `[Response] adv_handle:0, subevent:0, tx_status:0, num_rsp:1`

View File

@@ -0,0 +1,3 @@
idf_component_register(SRCS "ble_pawr_synchronizer_demo.c"
PRIV_REQUIRES bt nvs_flash
INCLUDE_DIRS ".")

View File

@@ -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

View File

@@ -0,0 +1,561 @@
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
/****************************************************************************
*
* This demo showcases BLE Periodic Advertising with Responses (PAwR) on the synchronizer side.
* The device performs extended scanning and creates periodic sync to the advertiser.
* After sync is established, it selects a list of subevents to synchronize to via
* esp_ble_gap_set_periodic_sync_subevent().
* For received subevents, it sends responses via esp_ble_gap_set_periodic_adv_response_data().
*
****************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/event_groups.h"
#include "esp_system.h"
#include "esp_log.h"
#include "nvs_flash.h"
#include "esp_bt.h"
#include "esp_gap_ble_api.h"
#include "esp_gatts_api.h"
#include "esp_bt_defs.h"
#include "esp_bt_main.h"
#include "esp_gatt_common_api.h"
#include "sdkconfig.h"
#include "freertos/semphr.h"
#define FUNC_SEND_WAIT_SEM(func, sem) do {\
esp_err_t __err_rc = (func);\
if (__err_rc != ESP_OK) { \
ESP_LOGE(LOG_TAG, "%s failed, error = %d", #func, __err_rc); \
return; \
} \
xSemaphoreTake(sem, portMAX_DELAY); \
} while(0);
#define LOG_TAG "PAwR_SYNC"
#define EXT_SCAN_DURATION 0
#define EXT_SCAN_PERIOD 0
#define PROFILE_A_APP_ID 0
#define SVC_INST_ID 0
/* Service UUID and Characteristic UUID */
#define GATTS_DEMO_CHAR_VAL_LEN_MAX 500
/* UUID values (must be variables, not macros, to take address) */
static const uint16_t GATTS_SERVICE_UUID_PAWR_SYNC = 0x00FF;
static const uint16_t GATTS_CHAR_UUID_PAWR_SYNC = 0xFF01;
/* Attributes State Machine */
enum {
IDX_SVC,
IDX_CHAR_A,
IDX_CHAR_VAL_A,
IDX_CHAR_CFG_A,
PAWR_SYNC_IDX_NB,
};
static char remote_device_name[ESP_BLE_ADV_NAME_LEN_MAX] = "ESP_EXTPAwR_ADV";
static SemaphoreHandle_t test_sem = NULL;
uint8_t rsp_slot_idx = 0;
/* Cached local address (read once during initialization) */
static esp_bd_addr_t cached_local_addr = {0};
static uint8_t cached_local_addr_type = 0;
static bool local_addr_cached = false;
// Forward declaration
static void gatts_profile_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param);
// GATTS profile structure
static struct gatts_profile_inst {
esp_gatts_cb_t gatts_cb;
uint16_t gatts_if;
uint16_t app_id;
uint16_t conn_id;
esp_bd_addr_t remote_bda;
} gl_profile_tab[1] = {
[PROFILE_A_APP_ID] = {
.gatts_cb = gatts_profile_event_handler, /* Set callback at initialization */
.gatts_if = ESP_GATT_IF_NONE, /* Not get the gatt_if, so initial is ESP_GATT_IF_NONE */
},
};
/* Handle table for service attributes */
static uint16_t pawr_sync_handle_table[PAWR_SYNC_IDX_NB];
/* UUID definitions */
static const uint16_t primary_service_uuid = ESP_GATT_UUID_PRI_SERVICE;
static const uint16_t character_declaration_uuid = ESP_GATT_UUID_CHAR_DECLARE;
static const uint16_t character_client_config_uuid = ESP_GATT_UUID_CHAR_CLIENT_CONFIG;
static const uint8_t char_prop_read_write_notify = ESP_GATT_CHAR_PROP_BIT_READ | ESP_GATT_CHAR_PROP_BIT_WRITE | ESP_GATT_CHAR_PROP_BIT_NOTIFY;
static const uint8_t char_value[4] = {0x00, 0x00, 0x00, 0x00};
static const uint8_t char_client_config_ccc[2] = {0x00, 0x00};
/* Full Database Description - Used to add attributes into the database */
static const esp_gatts_attr_db_t gatt_db[PAWR_SYNC_IDX_NB] =
{
// Service Declaration
[IDX_SVC] =
{{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&primary_service_uuid, ESP_GATT_PERM_READ,
sizeof(uint16_t), sizeof(GATTS_SERVICE_UUID_PAWR_SYNC), (uint8_t *)&GATTS_SERVICE_UUID_PAWR_SYNC}},
/* Characteristic Declaration */
[IDX_CHAR_A] =
{{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&character_declaration_uuid, ESP_GATT_PERM_READ,
sizeof(uint8_t), sizeof(uint8_t), (uint8_t *)&char_prop_read_write_notify}},
/* Characteristic Value */
[IDX_CHAR_VAL_A] =
{{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&GATTS_CHAR_UUID_PAWR_SYNC, ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE,
GATTS_DEMO_CHAR_VAL_LEN_MAX, sizeof(char_value), (uint8_t *)char_value}},
/* Client Characteristic Configuration Descriptor */
[IDX_CHAR_CFG_A] =
{{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&character_client_config_uuid, ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE,
sizeof(uint16_t), sizeof(char_client_config_ccc), (uint8_t *)char_client_config_ccc}},
};
static esp_ble_ext_scan_params_t ext_scan_params = {
.own_addr_type = BLE_ADDR_TYPE_PUBLIC,
.filter_policy = BLE_SCAN_FILTER_ALLOW_ALL,
.scan_duplicate = BLE_SCAN_DUPLICATE_ENABLE,
.cfg_mask = ESP_BLE_GAP_EXT_SCAN_CFG_UNCODE_MASK | ESP_BLE_GAP_EXT_SCAN_CFG_CODE_MASK,
.uncoded_cfg = {BLE_SCAN_TYPE_ACTIVE, 40, 40},
.coded_cfg = {BLE_SCAN_TYPE_ACTIVE, 40, 40},
};
static esp_ble_gap_periodic_adv_sync_params_t periodic_adv_sync_params = {
.filter_policy = 0,
.sid = 0,
.addr_type = BLE_ADDR_TYPE_RANDOM,
.skip = 10,
.sync_timeout = 1000,
};
bool periodic_sync = false;
static void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param)
{
switch (event) {
case ESP_GAP_BLE_SET_EXT_SCAN_PARAMS_COMPLETE_EVT:
xSemaphoreGive(test_sem);
ESP_LOGI(LOG_TAG, "Extended scanning params set, status %d", param->set_ext_scan_params.status);
break;
case ESP_GAP_BLE_EXT_SCAN_START_COMPLETE_EVT:
xSemaphoreGive(test_sem);
ESP_LOGI(LOG_TAG, "Extended scanning start, status %d", param->ext_scan_start.status);
// Cache local address after scan starts successfully (address is ready at this point)
if (param->ext_scan_start.status == ESP_BT_STATUS_SUCCESS && !local_addr_cached) {
esp_err_t addr_ret = esp_ble_gap_get_local_used_addr(cached_local_addr, &cached_local_addr_type);
if (addr_ret == ESP_OK) {
local_addr_cached = true;
ESP_LOGI(LOG_TAG, "Local address cached: "ESP_BD_ADDR_STR", type = %d",
ESP_BD_ADDR_HEX(cached_local_addr), cached_local_addr_type);
} else {
ESP_LOGW(LOG_TAG, "Failed to get local address, error = 0x%x", addr_ret);
}
} else if (param->ext_scan_start.status != ESP_BT_STATUS_SUCCESS) {
ESP_LOGE(LOG_TAG, "Extended scanning start failed, status = 0x%02x", param->ext_scan_start.status);
}
break;
case ESP_GAP_BLE_EXT_SCAN_STOP_COMPLETE_EVT:
xSemaphoreGive(test_sem);
ESP_LOGI(LOG_TAG, "Extended scanning stop, status %d", param->ext_scan_stop.status);
break;
case ESP_GAP_BLE_PERIODIC_ADV_CREATE_SYNC_COMPLETE_EVT:
ESP_LOGI(LOG_TAG, "Periodic advertising create sync, status %d", param->period_adv_create_sync.status);
break;
case ESP_GAP_BLE_PERIODIC_ADV_SYNC_CANCEL_COMPLETE_EVT:
ESP_LOGI(LOG_TAG, "Periodic advertising sync cancel, status %d", param->period_adv_sync_cancel.status);
break;
case ESP_GAP_BLE_PERIODIC_ADV_SYNC_TERMINATE_COMPLETE_EVT:
ESP_LOGI(LOG_TAG, "Periodic advertising sync terminate, status %d", param->period_adv_sync_term.status);
break;
case ESP_GAP_BLE_PERIODIC_ADV_SYNC_LOST_EVT:
ESP_LOGI(LOG_TAG, "Periodic advertising sync lost, sync handle %d", param->periodic_adv_sync_lost.sync_handle);
break;
case ESP_GAP_BLE_PERIODIC_ADV_SYNC_ESTAB_EVT:
ESP_LOGI(LOG_TAG, "Periodic advertising sync establish, status %d", param->periodic_adv_sync_estab.status);
ESP_LOGI(LOG_TAG, "address "ESP_BD_ADDR_STR"", ESP_BD_ADDR_HEX(param->periodic_adv_sync_estab.adv_addr));
ESP_LOGI(LOG_TAG, "sync handle %d sid %d perioic adv interval %d adv phy %d", param->periodic_adv_sync_estab.sync_handle,
param->periodic_adv_sync_estab.sid,
param->periodic_adv_sync_estab.period_adv_interval,
param->periodic_adv_sync_estab.adv_phy);
ESP_LOGI(LOG_TAG, "num_subevt %d subevt_interval %d rsp_slot_delay %d rsp_slot_spacing %d", param->periodic_adv_sync_estab.num_subevt,
param->periodic_adv_sync_estab.subevt_interval,
param->periodic_adv_sync_estab.rsp_slot_delay,
param->periodic_adv_sync_estab.rsp_slot_spacing);
uint8_t subevents[5] = {0, 1, 2, 3, 4};
esp_ble_per_sync_subevent_params sync_subevent_params = {0};
sync_subevent_params.sync_handle = param->periodic_adv_sync_estab.sync_handle;
sync_subevent_params.periodic_adv_properties = 0;
sync_subevent_params.num_subevents_to_sync = 5;
sync_subevent_params.subevent = malloc(sizeof(uint8_t) * 5);
if (!sync_subevent_params.subevent) {
ESP_LOGE(LOG_TAG, "No memory");
break;
}
for (uint8_t i = 0; i < 5; i++)
{
sync_subevent_params.subevent[i] = subevents[i];
}
esp_err_t ret = esp_ble_gap_set_periodic_sync_subevent(&sync_subevent_params);
if (ret != ESP_OK) {
ESP_LOGE(LOG_TAG, "Failed to set periodic sync subevent, error = 0x%x", ret);
}
// Free memory after API call (data is deep copied by the API)
free(sync_subevent_params.subevent);
break;
case ESP_GAP_BLE_EXT_ADV_REPORT_EVT: {
uint8_t *adv_name = NULL;
uint8_t adv_name_len = 0;
adv_name = esp_ble_resolve_adv_data_by_type(param->ext_adv_report.params.adv_data,
param->ext_adv_report.params.adv_data_len,
ESP_BLE_AD_TYPE_NAME_CMPL,
&adv_name_len);
if ((adv_name != NULL) && (memcmp(adv_name, remote_device_name, adv_name_len) == 0) && !periodic_sync) {
// Note: If there are multiple devices with the same device name, the device may sync to an unintended one.
// It is recommended to change the default device name to ensure it is unique.
periodic_sync = true;
char adv_temp_name[30] = {'0'};
size_t copy_len = (adv_name_len < sizeof(adv_temp_name)) ? adv_name_len : (sizeof(adv_temp_name) - 1);
memcpy(adv_temp_name, adv_name, copy_len);
adv_temp_name[copy_len] = '\0'; // Ensure null termination
ESP_LOGI(LOG_TAG, "Create sync with the peer device %s", adv_temp_name);
periodic_adv_sync_params.sid = param->ext_adv_report.params.sid;
periodic_adv_sync_params.addr_type = param->ext_adv_report.params.addr_type;
memcpy(periodic_adv_sync_params.addr, param->ext_adv_report.params.addr, sizeof(esp_bd_addr_t));
esp_ble_gap_periodic_adv_create_sync(&periodic_adv_sync_params);
}
}
break;
case ESP_GAP_BLE_PERIODIC_ADV_REPORT_EVT:
// ESP_LOGI(LOG_TAG, "Periodic adv report, sync handle %d, data status %d, data len %d, rssi %d", param->period_adv_report.params.sync_handle,
// param->period_adv_report.params.data_status,
// param->period_adv_report.params.data_length,
// param->period_adv_report.params.rssi);
//ESP_LOGI(LOG_TAG, "periodic_evt_counter %d subevt %d", param->period_adv_report.params.periodic_evt_counter, param->period_adv_report.params.subevt);
if (param->period_adv_report.params.subevt == 0xFF) {
break;
}
rsp_slot_idx = (uint8_t)((rsp_slot_idx + 1) % 5);
esp_ble_per_adv_response_data_params rsp_data_params = {0};
rsp_data_params.sync_handle = param->period_adv_report.params.sync_handle;
rsp_data_params.request_event = param->period_adv_report.params.periodic_evt_counter;
rsp_data_params.request_subevent = param->period_adv_report.params.subevt;
rsp_data_params.response_subevent = param->period_adv_report.params.subevt;
rsp_data_params.response_slot = rsp_slot_idx; // 0..4
// Use cached local address (read once after scan starts)
if (!local_addr_cached) {
ESP_LOGW(LOG_TAG, "Local address not cached yet, skipping address in response");
// Fall back to original behavior without address
// Note: data_length is uint8_t (max 255), so no overflow check needed
rsp_data_params.response_data_len = param->period_adv_report.params.data_length;
if (rsp_data_params.response_data_len > 0) {
rsp_data_params.response_data = malloc(rsp_data_params.response_data_len);
if (rsp_data_params.response_data) {
memcpy(rsp_data_params.response_data, param->period_adv_report.params.data, rsp_data_params.response_data_len);
esp_err_t ret = esp_ble_gap_set_periodic_adv_response_data(&rsp_data_params);
if (ret != ESP_OK) {
ESP_LOGE(LOG_TAG, "Failed to set periodic adv response data, error = 0x%x", ret);
}
free(rsp_data_params.response_data);
} else {
ESP_LOGE(LOG_TAG, "Failed to allocate memory for response data");
}
} else {
rsp_data_params.response_data = NULL;
esp_err_t ret = esp_ble_gap_set_periodic_adv_response_data(&rsp_data_params);
if (ret != ESP_OK) {
ESP_LOGE(LOG_TAG, "Failed to set periodic adv response data, error = 0x%x", ret);
}
}
break;
}
// Format: [0x00, 0x02, addr[5], addr[4], addr[3], addr[2], addr[1], addr[0], ...original_data]
// Total length: 8 bytes (address header) + original data length
const uint8_t ADDR_HEADER_LEN = 8;
// Note: data_length is uint8_t (max 255), and response_data_len is also uint8_t (max 255)
// Check if adding ADDR_HEADER_LEN would exceed uint8_t range
if (param->period_adv_report.params.data_length > (UINT8_MAX - ADDR_HEADER_LEN)) {
ESP_LOGE(LOG_TAG, "Data length too large, would exceed uint8_t range: %d", param->period_adv_report.params.data_length);
break;
}
rsp_data_params.response_data_len = ADDR_HEADER_LEN + param->period_adv_report.params.data_length;
rsp_data_params.response_data = malloc(rsp_data_params.response_data_len);
if (rsp_data_params.response_data) {
// Set address header: [0x00, 0x02, addr[5], addr[4], addr[3], addr[2], addr[1], addr[0]]
rsp_data_params.response_data[0] = 0x00;
rsp_data_params.response_data[1] = 0x02;
rsp_data_params.response_data[2] = cached_local_addr[5]; // MSB
rsp_data_params.response_data[3] = cached_local_addr[4];
rsp_data_params.response_data[4] = cached_local_addr[3];
rsp_data_params.response_data[5] = cached_local_addr[2];
rsp_data_params.response_data[6] = cached_local_addr[1];
rsp_data_params.response_data[7] = cached_local_addr[0]; // LSB
// Append original received data
if (param->period_adv_report.params.data_length > 0) {
memcpy(&rsp_data_params.response_data[ADDR_HEADER_LEN],
param->period_adv_report.params.data,
param->period_adv_report.params.data_length);
}
esp_err_t ret = esp_ble_gap_set_periodic_adv_response_data(&rsp_data_params);
if (ret != ESP_OK) {
ESP_LOGE(LOG_TAG, "Failed to set periodic adv response data, error = 0x%x", ret);
}
// Free memory after API call (data is deep copied by the API)
free(rsp_data_params.response_data);
} else {
ESP_LOGE(LOG_TAG, "Failed to allocate memory for response data");
}
break;
case ESP_GAP_BLE_SET_PERIODIC_ADV_SUBEVT_DATA_EVT:
ESP_LOGI(LOG_TAG, "Set PA subevent data, status %d", param->pa_subevt_data_evt.status);
break;
case ESP_GAP_BLE_SET_PERIODIC_ADV_RESPONSE_DATA_EVT:
if (param->pa_rsp_data_evt.status != ESP_BT_STATUS_SUCCESS)
{
ESP_LOGE(LOG_TAG, "Failed to set periodic adv response data, error = 0x%x", param->pa_rsp_data_evt.status);
}
// ESP_LOGI(LOG_TAG, "Set PA response data, status %d", param->pa_rsp_data_evt.status);
break;
case ESP_GAP_BLE_SET_PERIODIC_SYNC_SUBEVT_EVT:
ESP_LOGI(LOG_TAG, "PA sync subevent, status %d", param->pa_sync_subevt_evt.status);
break;
case ESP_GAP_BLE_PERIODIC_ADV_SUBEVT_DATA_REQUEST_EVT:
ESP_LOGI(LOG_TAG, "PA subevent data req, hdl %d start %d count %d", param->pa_subevt_data_req_evt.adv_handle, param->pa_subevt_data_req_evt.subevt_start, param->pa_subevt_data_req_evt.subevt_data_count);
break;
case ESP_GAP_BLE_PERIODIC_ADV_RESPONSE_REPORT_EVT:
ESP_LOGI(LOG_TAG, "Set Periodic advertising response report, adv_handle %d", param->pa_rsp_rpt_evt.adv_handle);
break;
default:
break;
}
}
/**
* @brief GATTS event handler
*/
static void gatts_profile_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param)
{
if (param == NULL) {
ESP_LOGE(LOG_TAG, "GATTS event handler received NULL param");
return;
}
switch (event) {
case ESP_GATTS_REG_EVT: {
ESP_LOGI(LOG_TAG, "GATTS register app_id %04x, status %d, gatts_if %d",
param->reg.app_id, param->reg.status, gatts_if);
if (param->reg.status == ESP_GATT_OK) {
gl_profile_tab[PROFILE_A_APP_ID].gatts_if = gatts_if;
// Create attribute table
esp_err_t create_attr_ret = esp_ble_gatts_create_attr_tab(gatt_db, gatts_if, PAWR_SYNC_IDX_NB, SVC_INST_ID);
if (create_attr_ret != ESP_OK) {
ESP_LOGE(LOG_TAG, "create attr table failed, error code = %x", create_attr_ret);
}
} else {
ESP_LOGE(LOG_TAG, "GATTS register failed, app_id %04x, status %d",
param->reg.app_id, param->reg.status);
}
break;
}
case ESP_GATTS_CREAT_ATTR_TAB_EVT: {
if (param->add_attr_tab.status != ESP_GATT_OK) {
ESP_LOGE(LOG_TAG, "create attribute table failed, error code=0x%x", param->add_attr_tab.status);
} else if (param->add_attr_tab.num_handle != PAWR_SYNC_IDX_NB) {
ESP_LOGE(LOG_TAG, "create attribute table abnormally, num_handle (%d) doesn't equal to PAWR_SYNC_IDX_NB(%d)",
param->add_attr_tab.num_handle, PAWR_SYNC_IDX_NB);
} else {
ESP_LOGI(LOG_TAG, "create attribute table successfully, the number handle = %d", param->add_attr_tab.num_handle);
memcpy(pawr_sync_handle_table, param->add_attr_tab.handles, sizeof(pawr_sync_handle_table));
esp_ble_gatts_start_service(pawr_sync_handle_table[IDX_SVC]);
ESP_LOGI(LOG_TAG, "Service started, service_handle = %d", pawr_sync_handle_table[IDX_SVC]);
}
break;
}
case ESP_GATTS_READ_EVT:
ESP_LOGI(LOG_TAG, "ESP_GATTS_READ_EVT, handle = %d", param->read.handle);
break;
case ESP_GATTS_WRITE_EVT:
ESP_LOGI(LOG_TAG, "ESP_GATTS_WRITE_EVT, handle = %d, len = %d", param->write.handle, param->write.len);
if (param->write.value != NULL && param->write.len > 0) {
ESP_LOG_BUFFER_HEX(LOG_TAG, param->write.value, param->write.len);
}
// Handle CCCD write (notify enable/disable)
// Check if handle table is initialized (service created successfully)
if (pawr_sync_handle_table[IDX_CHAR_CFG_A] != 0 &&
pawr_sync_handle_table[IDX_CHAR_CFG_A] == param->write.handle &&
param->write.len == 2 && param->write.value != NULL) {
uint16_t descr_value = param->write.value[1] << 8 | param->write.value[0];
if (descr_value == 0x0001) {
ESP_LOGI(LOG_TAG, "notify enable");
} else if (descr_value == 0x0000) {
ESP_LOGI(LOG_TAG, "notify disable");
}
}
if (param->write.need_rsp) {
esp_ble_gatts_send_response(gatts_if, param->write.conn_id, param->write.trans_id, ESP_GATT_OK, NULL);
}
break;
case ESP_GATTS_START_EVT:
ESP_LOGI(LOG_TAG, "SERVICE_START_EVT, status %d, service_handle %d", param->start.status, param->start.service_handle);
break;
case ESP_GATTS_CONNECT_EVT: {
ESP_LOGI(LOG_TAG, "[Connection established], conn_id = 0x%04x", param->connect.conn_id);
gl_profile_tab[PROFILE_A_APP_ID].conn_id = param->connect.conn_id;
memcpy(gl_profile_tab[PROFILE_A_APP_ID].remote_bda, param->connect.remote_bda, ESP_BD_ADDR_LEN);
ESP_LOGI(LOG_TAG, "Remote Address: "ESP_BD_ADDR_STR"", ESP_BD_ADDR_HEX(param->connect.remote_bda));
break;
}
case ESP_GATTS_DISCONNECT_EVT:
ESP_LOGI(LOG_TAG, "[Disconnected], conn_id = 0x%04x, reason = 0x%02x",
param->disconnect.conn_id, param->disconnect.reason);
gl_profile_tab[PROFILE_A_APP_ID].conn_id = 0xFFFF;
break;
case ESP_GATTS_MTU_EVT:
ESP_LOGI(LOG_TAG, "MTU update event, conn_id = 0x%04x, mtu = %d",
param->mtu.conn_id, param->mtu.mtu);
break;
default:
break;
}
}
/**
* @brief GATTS callback function
*/
static void esp_gatts_cb(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param)
{
if (param == NULL) {
ESP_LOGE(LOG_TAG, "GATTS callback received NULL param");
return;
}
/* If event is register event, store the gatts_if for each profile */
if (event == ESP_GATTS_REG_EVT) {
if (param->reg.status == ESP_GATT_OK) {
gl_profile_tab[param->reg.app_id].gatts_if = gatts_if;
} else {
ESP_LOGE(LOG_TAG, "reg app failed, app_id %04x, status %d",
param->reg.app_id, param->reg.status);
return;
}
}
/* If the gatts_if equal to profile A, call profile A cb handler */
do {
int idx;
for (idx = 0; idx < 1; idx++) {
if (gatts_if == ESP_GATT_IF_NONE || /* ESP_GATT_IF_NONE, not specify a certain gatt_if, need to call every profile cb function */
gatts_if == gl_profile_tab[idx].gatts_if) {
if (gl_profile_tab[idx].gatts_cb) {
gl_profile_tab[idx].gatts_cb(event, gatts_if, param);
}
}
}
} while (0);
}
void app_main(void)
{
esp_err_t ret;
// Initialize NVS.
ret = nvs_flash_init();
if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
ESP_ERROR_CHECK(nvs_flash_erase());
ret = nvs_flash_init();
}
ESP_ERROR_CHECK( ret );
#if CONFIG_EXAMPLE_CI_PIPELINE_ID
memcpy(remote_device_name, esp_bluedroid_get_example_name(), sizeof(remote_device_name));
#endif
ESP_ERROR_CHECK(esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT));
esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT();
ret = esp_bt_controller_init(&bt_cfg);
if (ret) {
ESP_LOGE(LOG_TAG, "%s initialize controller failed: %s", __func__, esp_err_to_name(ret));
return;
}
ret = esp_bt_controller_enable(ESP_BT_MODE_BLE);
if (ret) {
ESP_LOGE(LOG_TAG, "%s enable controller failed: %s", __func__, esp_err_to_name(ret));
return;
}
esp_bluedroid_config_t cfg = BT_BLUEDROID_INIT_CONFIG_DEFAULT();
ret = esp_bluedroid_init_with_cfg(&cfg);
if (ret) {
ESP_LOGE(LOG_TAG, "%s init bluetooth failed: %s", __func__, esp_err_to_name(ret));
return;
}
ret = esp_bluedroid_enable();
if (ret) {
ESP_LOGE(LOG_TAG, "%s enable bluetooth failed: %s", __func__, esp_err_to_name(ret));
return;
}
ret = esp_ble_gap_register_callback(gap_event_handler);
if (ret){
ESP_LOGE(LOG_TAG, "gap register error, error code = %x", ret);
return;
}
// Register GATTS callback
ret = esp_ble_gatts_register_callback(esp_gatts_cb);
if (ret){
ESP_LOGE(LOG_TAG, "gatts register error, error code = %x", ret);
return;
}
// Register GATTS app (this will trigger ESP_GATTS_REG_EVT event immediately)
ret = esp_ble_gatts_app_register(PROFILE_A_APP_ID);
if (ret){
ESP_LOGE(LOG_TAG, "gatts app register error, error code = %x", ret);
return;
}
vTaskDelay(200 / portTICK_PERIOD_MS);
test_sem = xSemaphoreCreateBinary();
FUNC_SEND_WAIT_SEM(esp_ble_gap_set_ext_scan_params(&ext_scan_params), test_sem);
FUNC_SEND_WAIT_SEM(esp_ble_gap_start_ext_scan(EXT_SCAN_DURATION, EXT_SCAN_PERIOD), test_sem);
return;
}

View File

@@ -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}

View File

@@ -0,0 +1,2 @@
CONFIG_EXAMPLE_CI_ID=8
CONFIG_EXAMPLE_CI_PIPELINE_ID=${CI_PIPELINE_ID}

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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)

View File

@@ -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.

View File

@@ -0,0 +1,3 @@
idf_component_register(SRCS "periodic_adv_with_cte_demo.c"
PRIV_REQUIRES bt nvs_flash
INCLUDE_DIRS ".")

View File

@@ -0,0 +1,266 @@
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
/****************************************************************************
*
* This demo showcases BLE GATT server. It can send adv data, be connected by client.
* Run the gatt_client demo, the client demo will automatically connect to the gatt_server demo.
* Client demo will enable gatt_server's notify after connection. The two devices will then exchange
* data.
*
****************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/event_groups.h"
#include "esp_system.h"
#include "esp_log.h"
#include "nvs_flash.h"
#include "esp_bt.h"
#include "esp_gap_ble_api.h"
#include "esp_gatts_api.h"
#include "esp_bt_defs.h"
#include "esp_bt_main.h"
#include "esp_gatt_common_api.h"
#include "sdkconfig.h"
#include "freertos/semphr.h"
#include "esp_ble_cte_api.h"
#define LOG_TAG "CTE_TEST"
#define FUNC_SEND_WAIT_SEM(func, sem) do {\
esp_err_t __err_rc = (func);\
if (__err_rc != ESP_OK) { \
ESP_LOGE(LOG_TAG, "%s, message send fail, error = %d", __func__, __err_rc); \
} \
xSemaphoreTake(sem, portMAX_DELAY); \
} while(0);
#define EXT_ADV_HANDLE 0
#define NUM_EXT_ADV 1
static SemaphoreHandle_t test_sem = NULL;
uint8_t addr_2m[6] = {0xc0, 0xde, 0x52, 0x00, 0x00, 0x02};
esp_ble_gap_ext_adv_params_t ext_adv_params_2M = {
.type = ESP_BLE_GAP_SET_EXT_ADV_PROP_NONCONN_NONSCANNABLE_UNDIRECTED,
.interval_min = 0x30,
.interval_max = 0x30,
.channel_map = ADV_CHNL_ALL,
.filter_policy = ADV_FILTER_ALLOW_SCAN_ANY_CON_ANY,
.primary_phy = ESP_BLE_GAP_PHY_1M,
.max_skip = 0,
.secondary_phy = ESP_BLE_GAP_PHY_2M,
.sid = 0,
.scan_req_notif = false,
.own_addr_type = BLE_ADDR_TYPE_RANDOM,
.tx_power = EXT_ADV_TX_PWR_NO_PREFERENCE,
};
static esp_ble_gap_periodic_adv_params_t periodic_adv_params = {
.interval_min = 0x40, // 80 ms interval
.interval_max = 0x40,
.properties = 0, // Do not include TX power
};
static uint8_t periodic_adv_raw_data[] = {
0x02, 0x01, 0x06,
0x02, 0x0a, 0xeb,
0x03, 0x03, 0xab, 0xcd,
0x12, 0x09, 'E', 'S', 'P', '_', 'C', 'T', 'E', '_', 'B', 'L', 'U',
'E', 'D', 'R', 'O', 'I', 'D'
};
static uint8_t raw_ext_adv_data_2m[] = {
0x02, 0x01, 0x06,
0x02, 0x0a, 0xeb,
0x12, 0x09, 'E', 'S', 'P', '_', 'C', 'T', 'E', '_', 'B', 'L', 'U',
'E', 'D', 'R', 'O', 'I', 'D'
};
static esp_ble_gap_ext_adv_t ext_adv[1] = {
// instance, duration, period
[0] = {EXT_ADV_HANDLE, 0, 0},
};
static uint8_t antenna_ids[5] = {0x00, 0x01, 0x02, 0x03, 0x04};
static esp_ble_cte_connless_trans_params_t cte_trans_params = {
.adv_handle = 0,
.cte_len = ESP_BLE_CTE_MAX_CTE_LENGTH,
.cte_type = ESP_BLE_CTE_TYPE_AOA,
.cte_count = ESP_BLE_CTE_MIN_CTE_COUNT,
.switching_pattern_len = sizeof(antenna_ids),
.antenna_ids = antenna_ids,
};
static esp_ble_cte_trans_enable_params_t cte_trans_enable = {
.adv_handle = 0,
.cte_enable = ESP_BLE_CTE_ADV_WITH_CTE_ENABLE,
};
static uint8_t periodic_adv_hdl = 0xff;
static void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param)
{
switch (event) {
case ESP_GAP_BLE_EXT_ADV_SET_RAND_ADDR_COMPLETE_EVT:
xSemaphoreGive(test_sem);
ESP_LOGI(LOG_TAG, "ESP_GAP_BLE_EXT_ADV_SET_RAND_ADDR_COMPLETE_EVT, status %d", param->ext_adv_set_rand_addr.status);
break;
case ESP_GAP_BLE_EXT_ADV_SET_PARAMS_COMPLETE_EVT:
xSemaphoreGive(test_sem);
ESP_LOGI(LOG_TAG, "ESP_GAP_BLE_EXT_ADV_SET_PARAMS_COMPLETE_EVT, status %d", param->ext_adv_set_params.status);
break;
case ESP_GAP_BLE_EXT_ADV_DATA_SET_COMPLETE_EVT:
xSemaphoreGive(test_sem);
ESP_LOGI(LOG_TAG, "ESP_GAP_BLE_EXT_ADV_DATA_SET_COMPLETE_EVT, status %d", param->ext_adv_data_set.status);
break;
case ESP_GAP_BLE_EXT_SCAN_RSP_DATA_SET_COMPLETE_EVT:
xSemaphoreGive(test_sem);
ESP_LOGI(LOG_TAG, "ESP_GAP_BLE_EXT_SCAN_RSP_DATA_SET_COMPLETE_EVT, status %d", param->scan_rsp_set.status);
break;
case ESP_GAP_BLE_EXT_ADV_START_COMPLETE_EVT:
xSemaphoreGive(test_sem);
ESP_LOGI(LOG_TAG, "ESP_GAP_BLE_EXT_ADV_START_COMPLETE_EVT, status %d", param->ext_adv_start.status);
break;
case ESP_GAP_BLE_EXT_ADV_STOP_COMPLETE_EVT:
xSemaphoreGive(test_sem);
ESP_LOGI(LOG_TAG, "ESP_GAP_BLE_EXT_ADV_STOP_COMPLETE_EVT, status %d", param->ext_adv_stop.status);
break;
case ESP_GAP_BLE_PERIODIC_ADV_SET_PARAMS_COMPLETE_EVT:
periodic_adv_hdl = param->peroid_adv_set_params.instance;
xSemaphoreGive(test_sem);
ESP_LOGI(LOG_TAG, "ESP_GAP_BLE_PERIODIC_ADV_SET_PARAMS_COMPLETE_EVT, status %d", param->peroid_adv_set_params.status);
break;
case ESP_GAP_BLE_PERIODIC_ADV_DATA_SET_COMPLETE_EVT:
xSemaphoreGive(test_sem);
ESP_LOGI(LOG_TAG, "ESP_GAP_BLE_PERIODIC_ADV_DATA_SET_COMPLETE_EVT, status %d", param->period_adv_data_set.status);
break;
case ESP_GAP_BLE_PERIODIC_ADV_START_COMPLETE_EVT:
xSemaphoreGive(test_sem);
ESP_LOGI(LOG_TAG, "ESP_GAP_BLE_PERIODIC_ADV_START_COMPLETE_EVT, status %d", param->period_adv_start.status);
break;
default:
break;
}
}
static void cte_event_handler(esp_ble_cte_cb_event_t event, esp_ble_cte_cb_param_t *param)
{
switch (event) {
case ESP_BLE_CTE_SET_CONNLESS_TRANS_PARAMS_CMPL_EVT:
xSemaphoreGive(test_sem);
ESP_LOGI(LOG_TAG, "ESP_BLE_CTE_SET_CONNLESS_TRANS_PARAMS_CMPL_EVT, status %d", param->set_trans_params_cmpl.status);
break;
case ESP_BLE_CTE_SET_CONNLESS_TRANS_ENABLE_CMPL_EVT:
xSemaphoreGive(test_sem);
ESP_LOGI(LOG_TAG, "ESP_BLE_CTE_SET_CONNLESS_TRANS_ENABLE_CMPL_EVT, status %d", param->set_trans_enable_cmpl.status);
break;
case ESP_BLE_CTE_SET_CONNLESS_IQ_SAMPLING_ENABLE_CMPL_EVT:
break;
case ESP_BLE_CTE_READ_ANT_INFOR_CMPL_EVT:
break;
case ESP_BLE_CTE_CONNLESS_IQ_REPORT_EVT:
break;
default:
break;
}
}
void app_main(void)
{
esp_err_t ret;
// Initialize NVS.
ret = nvs_flash_init();
if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
ESP_ERROR_CHECK(nvs_flash_erase());
ret = nvs_flash_init();
}
ESP_ERROR_CHECK( ret );
ESP_ERROR_CHECK(esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT));
esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT();
ret = esp_bt_controller_init(&bt_cfg);
if (ret) {
ESP_LOGE(LOG_TAG, "%s initialize controller failed: %s", __func__, esp_err_to_name(ret));
return;
}
ret = esp_bt_controller_enable(ESP_BT_MODE_BLE);
if (ret) {
ESP_LOGE(LOG_TAG, "%s enable controller failed: %s", __func__, esp_err_to_name(ret));
return;
}
esp_bluedroid_config_t bluedroid_cfg = BT_BLUEDROID_INIT_CONFIG_DEFAULT();
ret = esp_bluedroid_init_with_cfg(&bluedroid_cfg);
if (ret) {
ESP_LOGE(LOG_TAG, "%s init bluetooth failed: %s", __func__, esp_err_to_name(ret));
return;
}
ret = esp_bluedroid_enable();
if (ret) {
ESP_LOGE(LOG_TAG, "%s enable bluetooth failed: %s", __func__, esp_err_to_name(ret));
return;
}
ret = esp_ble_gap_register_callback(gap_event_handler);
if (ret){
ESP_LOGE(LOG_TAG, "gap register error, error code = %x", ret);
return;
}
esp_ble_cte_register_callback(cte_event_handler);
vTaskDelay(200 / portTICK_PERIOD_MS);
test_sem = xSemaphoreCreateBinary();
// 2M phy extend adv, Connectable advertising
FUNC_SEND_WAIT_SEM(esp_ble_gap_ext_adv_set_params(EXT_ADV_HANDLE, &ext_adv_params_2M), test_sem);
FUNC_SEND_WAIT_SEM(esp_ble_gap_ext_adv_set_rand_addr(EXT_ADV_HANDLE, addr_2m), test_sem);
FUNC_SEND_WAIT_SEM(esp_ble_gap_config_ext_adv_data_raw(EXT_ADV_HANDLE, sizeof(raw_ext_adv_data_2m), &raw_ext_adv_data_2m[0]), test_sem);
// start all adv
FUNC_SEND_WAIT_SEM(esp_ble_gap_ext_adv_start(NUM_EXT_ADV, &ext_adv[0]), test_sem);
// set periodic adv param
FUNC_SEND_WAIT_SEM(esp_ble_gap_periodic_adv_set_params(EXT_ADV_HANDLE, &periodic_adv_params), test_sem);
cte_trans_params.adv_handle = periodic_adv_hdl;
FUNC_SEND_WAIT_SEM(esp_ble_cte_set_connectionless_trans_params(&cte_trans_params), test_sem);
cte_trans_enable.adv_handle = periodic_adv_hdl;
FUNC_SEND_WAIT_SEM(esp_ble_cte_set_connectionless_trans_enable(&cte_trans_enable), test_sem);
#if (CONFIG_BT_BLE_FEAT_PERIODIC_ADV_ENH)
// set periodic adv raw data
FUNC_SEND_WAIT_SEM(esp_ble_gap_config_periodic_adv_data_raw(EXT_ADV_HANDLE, sizeof(periodic_adv_raw_data), &periodic_adv_raw_data[0], false), test_sem);
// start periodic adv, include the ADI field in AUX_SYNC_IND PDUs
FUNC_SEND_WAIT_SEM(esp_ble_gap_periodic_adv_start(EXT_ADV_HANDLE, true), test_sem);
while (1) {
vTaskDelay(2000 / portTICK_PERIOD_MS);
// just update the Advertising DID of the periodic advertising, unchanged data
FUNC_SEND_WAIT_SEM(esp_ble_gap_config_periodic_adv_data_raw(EXT_ADV_HANDLE, 0, NULL, true), test_sem);
}
#else
// set periodic adv raw data
FUNC_SEND_WAIT_SEM(esp_ble_gap_config_periodic_adv_data_raw(EXT_ADV_HANDLE, sizeof(periodic_adv_raw_data), &periodic_adv_raw_data[0]), test_sem);
FUNC_SEND_WAIT_SEM(esp_ble_gap_periodic_adv_start(EXT_ADV_HANDLE), test_sem);
#endif
return;
}

View File

@@ -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

View File

@@ -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)

View File

@@ -0,0 +1,86 @@
| Supported Targets | ESP32-C5 | ESP32-C61 | ESP32-H2 |
| ----------------- | -------- | --------- | -------- |
# ESP-IDF BLE Periodic Sync with CTE Example
This example demonstrates how to use BLE Periodic Synchronization with Constant Tone Extension (CTE) feature using Bluedroid host API.
## What is BLE Periodic Sync with CTE?
BLE Periodic Sync with CTE combines periodic synchronization with Constant Tone Extension to enable Direction Finding (AoA/AoD) functionality when synchronizing with periodic advertising. This allows devices to determine the direction of a Bluetooth signal during periodic advertising synchronization.
## How It Works
1. The device starts extended scanning
2. The device creates periodic sync with a periodic advertiser
3. The device configures CTE receive parameters for periodic sync
4. The device receives periodic advertising packets with CTE
5. IQ samples are received for direction finding
## API Usage
- `esp_ble_gap_ext_scan_start()` - Start extended scanning
- `esp_ble_gap_periodic_adv_create_sync()` - Create periodic sync
- `esp_ble_cte_set_periodic_adv_receive_params()` - Set CTE receive parameters for periodic sync
- `esp_ble_cte_periodic_adv_request_enable()` - Enable CTE request for periodic sync
## Configuration
The example requires BLE 5.0 CTE and periodic advertising features to be enabled:
- `CONFIG_BT_BLE_50_FEATURES_SUPPORTED=y`
- `CONFIG_BT_BLE_50_EXTEND_SCAN_EN=y`
- `CONFIG_BT_BLE_50_PERIODIC_ADV_EN=y`
- `CONFIG_BT_BLE_FEAT_CTE_EN=y`
- `CONFIG_BT_LE_CTE_FEATURE_ENABLED=y`
To test this demo, you can run the [ble_periodic_adv_with_cte](../ble_periodic_adv_with_cte) example on another device, which starts periodic advertising with CTE.
## How to Use Example
Before project configuration and build, be sure to set the correct chip target using:
```bash
idf.py set-target <chip_name>
```
### Hardware Required
* A development board with ESP32-C5, ESP32-C61, or ESP32-H2 SoC that supports BLE 5.0 CTE
* A USB cable for Power supply and programming
See [Development Boards](https://www.espressif.com/en/products/devkits) for more information about it.
### Build and Flash
Run `idf.py -p PORT flash monitor` to build, flash and monitor the project.
(To exit the serial monitor, type ``Ctrl-]``.)
See the [Getting Started Guide](https://idf.espressif.com/) for full steps to configure and use ESP-IDF to build projects.
## Example Output
The example will output periodic sync and CTE-related events including:
- Extended scan start completion
- Periodic sync creation completion
- Periodic sync establishment
- Periodic advertising reports
- CTE parameter configuration completion
- CTE request enable completion
- IQ sample reports for direction finding
```
I (xxx) CTE_TEST: Extended scan started
I (xxx) CTE_TEST: Periodic sync created
I (xxx) CTE_TEST: Periodic sync established
I (xxx) CTE_TEST: Periodic adv report received
I (xxx) CTE_TEST: CTE set periodic adv receive params, status 0
I (xxx) CTE_TEST: CTE periodic adv request enable, status 0
I (xxx) CTE_TEST: CTE periodic IQ report received
...
```
## Troubleshooting
For any technical queries, please open an [issue](https://github.com/espressif/esp-idf/issues) on GitHub. We will get back to you soon.

View File

@@ -0,0 +1,3 @@
idf_component_register(SRCS "periodic_sync_with_cte_demo.c"
PRIV_REQUIRES bt nvs_flash
INCLUDE_DIRS ".")

View File

@@ -0,0 +1,246 @@
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
/****************************************************************************
*
* This demo showcases BLE GATT server. It can send adv data, be connected by client.
* Run the gatt_client demo, the client demo will automatically connect to the gatt_server demo.
* Client demo will enable gatt_server's notify after connection. The two devices will then exchange
* data.
*
****************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/event_groups.h"
#include "esp_system.h"
#include "esp_log.h"
#include "nvs_flash.h"
#include "esp_bt.h"
#include "esp_gap_ble_api.h"
#include "esp_gatts_api.h"
#include "esp_bt_defs.h"
#include "esp_bt_main.h"
#include "esp_gatt_common_api.h"
#include "sdkconfig.h"
#include "freertos/semphr.h"
#include "esp_ble_cte_api.h"
#define FUNC_SEND_WAIT_SEM(func, sem) do {\
esp_err_t __err_rc = (func);\
if (__err_rc != ESP_OK) { \
ESP_LOGE(LOG_TAG, "%s, message send fail, error = %d", __func__, __err_rc); \
} \
xSemaphoreTake(sem, portMAX_DELAY); \
} while(0);
#define LOG_TAG "PERIODIC_SYNC"
#define EXT_SCAN_DURATION 0
#define EXT_SCAN_PERIOD 0
static char remote_device_name[ESP_BLE_ADV_NAME_LEN_MAX] = "ESP_CTE_BLUEDROID";
static SemaphoreHandle_t test_sem = NULL;
static esp_ble_ext_scan_params_t ext_scan_params = {
.own_addr_type = BLE_ADDR_TYPE_PUBLIC,
.filter_policy = BLE_SCAN_FILTER_ALLOW_ALL,
.scan_duplicate = BLE_SCAN_DUPLICATE_ENABLE,
.cfg_mask = ESP_BLE_GAP_EXT_SCAN_CFG_UNCODE_MASK | ESP_BLE_GAP_EXT_SCAN_CFG_CODE_MASK,
.uncoded_cfg = {BLE_SCAN_TYPE_ACTIVE, 40, 40},
.coded_cfg = {BLE_SCAN_TYPE_ACTIVE, 40, 40},
};
static esp_ble_gap_periodic_adv_sync_params_t periodic_adv_sync_params = {
.filter_policy = 0,
.sid = 0,
.addr_type = BLE_ADDR_TYPE_RANDOM,
.skip = 10,
.sync_timeout = 1000,
};
uint8_t antenna_ids[4] = {0x00, 0x04, 0x08, 0x0D};
static esp_ble_cte_iq_sampling_params_t iq_sampling_en = {
.sync_handle = 0,
.sampling_en = ESP_BLE_CTE_SAMPLING_ENABLE,
.slot_dur = ESP_BLE_CTE_SLOT_DURATION_2US,
.max_sampled_ctes = ESP_BLE_CTE_MIN_SAMPLED_CTES,
.switching_pattern_len = sizeof(antenna_ids),
.antenna_ids = &antenna_ids[0],
};
bool periodic_sync = false;
static void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param)
{
switch (event) {
case ESP_GAP_BLE_SET_EXT_SCAN_PARAMS_COMPLETE_EVT:
xSemaphoreGive(test_sem);
ESP_LOGI(LOG_TAG, "Extended scanning params set, status %d", param->set_ext_scan_params.status);
break;
case ESP_GAP_BLE_EXT_SCAN_START_COMPLETE_EVT:
xSemaphoreGive(test_sem);
ESP_LOGI(LOG_TAG, "Extended scanning start, status %d", param->ext_scan_start.status);
break;
case ESP_GAP_BLE_EXT_SCAN_STOP_COMPLETE_EVT:
xSemaphoreGive(test_sem);
ESP_LOGI(LOG_TAG, "Extended scanning stop, status %d", param->ext_scan_stop.status);
break;
case ESP_GAP_BLE_PERIODIC_ADV_CREATE_SYNC_COMPLETE_EVT:
ESP_LOGI(LOG_TAG, "Periodic advertising create sync, status %d", param->period_adv_create_sync.status);
break;
case ESP_GAP_BLE_PERIODIC_ADV_SYNC_CANCEL_COMPLETE_EVT:
ESP_LOGI(LOG_TAG, "Periodic advertising sync cancel, status %d", param->period_adv_sync_cancel.status);
break;
case ESP_GAP_BLE_PERIODIC_ADV_SYNC_TERMINATE_COMPLETE_EVT:
ESP_LOGI(LOG_TAG, "Periodic advertising sync terminate, status %d", param->period_adv_sync_term.status);
break;
case ESP_GAP_BLE_PERIODIC_ADV_SYNC_LOST_EVT:
ESP_LOGI(LOG_TAG, "Periodic advertising sync lost, sync handle %d", param->periodic_adv_sync_lost.sync_handle);
break;
case ESP_GAP_BLE_PERIODIC_ADV_SYNC_ESTAB_EVT:
ESP_LOGI(LOG_TAG, "Periodic advertising sync establish, status %d", param->periodic_adv_sync_estab.status);
ESP_LOGI(LOG_TAG, "address "ESP_BD_ADDR_STR"", ESP_BD_ADDR_HEX(param->periodic_adv_sync_estab.adv_addr));
ESP_LOGI(LOG_TAG, "sync handle %d sid %d perioic adv interval %d adv phy %d", param->periodic_adv_sync_estab.sync_handle,
param->periodic_adv_sync_estab.sid,
param->periodic_adv_sync_estab.period_adv_interval,
param->periodic_adv_sync_estab.adv_phy);
iq_sampling_en.sync_handle = param->periodic_adv_sync_estab.sync_handle;
esp_ble_cte_set_connectionless_iq_sampling_enable(&iq_sampling_en);
break;
case ESP_GAP_BLE_EXT_ADV_REPORT_EVT: {
uint8_t *adv_name = NULL;
uint8_t adv_name_len = 0;
adv_name = esp_ble_resolve_adv_data_by_type(param->ext_adv_report.params.adv_data,
param->ext_adv_report.params.adv_data_len,
ESP_BLE_AD_TYPE_NAME_CMPL,
&adv_name_len);
if ((adv_name != NULL) && (memcmp(adv_name, remote_device_name, adv_name_len) == 0) && !periodic_sync) {
// Note: If there are multiple devices with the same device name, the device may sync to an unintended one.
// It is recommended to change the default device name to ensure it is unique.
periodic_sync = true;
char adv_temp_name[30] = {'0'};
memcpy(adv_temp_name, adv_name, adv_name_len);
ESP_LOGI(LOG_TAG, "Create sync with the peer device %s", adv_temp_name);
periodic_adv_sync_params.sid = param->ext_adv_report.params.sid;
periodic_adv_sync_params.addr_type = param->ext_adv_report.params.addr_type;
memcpy(periodic_adv_sync_params.addr, param->ext_adv_report.params.addr, sizeof(esp_bd_addr_t));
esp_ble_gap_periodic_adv_create_sync(&periodic_adv_sync_params);
}
}
break;
case ESP_GAP_BLE_PERIODIC_ADV_REPORT_EVT:
ESP_LOGI(LOG_TAG, "Periodic adv report, sync handle %d, data status %d, data len %d, rssi %d", param->period_adv_report.params.sync_handle,
param->period_adv_report.params.data_status,
param->period_adv_report.params.data_length,
param->period_adv_report.params.rssi);
//esp_log_buffer_hex("data", param->period_adv_report.params.data, param->period_adv_report.params.data_length);
break;
default:
break;
}
}
static void cte_event_handler(esp_ble_cte_cb_event_t event, esp_ble_cte_cb_param_t *param)
{
switch (event) {
case ESP_BLE_CTE_SET_CONNLESS_TRANS_PARAMS_CMPL_EVT:
ESP_LOGI(LOG_TAG, "ESP_BLE_CTE_SET_CONNLESS_TRANS_PARAMS_CMPL_EVT, status %d", param->set_trans_params_cmpl.status);
break;
case ESP_BLE_CTE_SET_CONNLESS_TRANS_ENABLE_CMPL_EVT:
ESP_LOGI(LOG_TAG, "ESP_BLE_CTE_SET_CONNLESS_TRANS_ENABLE_CMPL_EVT, status %d", param->set_trans_enable_cmpl.status);
break;
case ESP_BLE_CTE_SET_CONNLESS_IQ_SAMPLING_ENABLE_CMPL_EVT:
ESP_LOGI(LOG_TAG, "CTE IQ sampling enable, status %d", param->iq_sampling_enable_cmpl.status);
break;
case ESP_BLE_CTE_READ_ANT_INFOR_CMPL_EVT:
ESP_LOGI(LOG_TAG, "CTE read ant information cmpl, status %d", param->read_ant_infor_cmpl.status);
break;
case ESP_BLE_CTE_CONNLESS_IQ_REPORT_EVT:
ESP_LOGI(LOG_TAG, "sync_handle %d channel_idx %d rssi %d rssi_ant_id %d cte_type %d slot_dur %d pkt_status %d periodic_evt_counter %d sample_count %d",
param->connless_iq_rpt.sync_handle, param->connless_iq_rpt.channel_idx, param->connless_iq_rpt.rssi,
param->connless_iq_rpt.rssi_ant_id, param->connless_iq_rpt.cte_type, param->connless_iq_rpt.slot_dur,
param->connless_iq_rpt.pkt_status, param->connless_iq_rpt.periodic_evt_counter, param->connless_iq_rpt.sample_count);
ESP_LOG_BUFFER_HEX("i_sample", &param->connless_iq_rpt.i_sample[0], param->connless_iq_rpt.sample_count);
ESP_LOG_BUFFER_HEX("q_sample", &param->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;
}

View File

@@ -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

View File

@@ -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)

View File

@@ -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

View File

@@ -0,0 +1,3 @@
idf_component_register(SRCS "main.c"
PRIV_REQUIRES bt nvs_flash
INCLUDE_DIRS ".")

View File

@@ -0,0 +1,369 @@
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
/****************************************************************************
*
* This demo showcases BLE Power Control feature on Central side.
* It scans for BLE devices and connects to a peripheral device. After connection,
* it reads transmit power levels, sets path loss reporting parameters, and
* enables power reporting to monitor and control power consumption.
*
****************************************************************************/
#include <stdint.h>
#include <string.h>
#include <stdbool.h>
#include <stdio.h>
#include "nvs.h"
#include "nvs_flash.h"
#include "esp_bt.h"
#include "esp_gap_ble_api.h"
#include "esp_gattc_api.h"
#include "esp_gatt_defs.h"
#include "esp_bt_main.h"
#include "esp_gatt_common_api.h"
#include "esp_log.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#define TAG "BLE_POWER_CONTROL_CENTRAL"
#define REMOTE_DEVICE_NAME "ESP_PWR_CTL_PRH"
#define PROFILE_NUM 1
#define PROFILE_A_APP_ID 0
static bool connect = false;
static uint16_t conn_handle = 0xFFFF;
static esp_ble_ext_scan_params_t ext_scan_params = {
.own_addr_type = BLE_ADDR_TYPE_PUBLIC,
.filter_policy = BLE_SCAN_FILTER_ALLOW_ALL,
.scan_duplicate = BLE_SCAN_DUPLICATE_DISABLE,
.cfg_mask = ESP_BLE_GAP_EXT_SCAN_CFG_UNCODE_MASK,
.uncoded_cfg = {BLE_SCAN_TYPE_ACTIVE, 0x40, 0x40},
.coded_cfg = {BLE_SCAN_TYPE_ACTIVE, 0x40, 0x40},
};
// Connection parameters for different PHYs
static const esp_ble_conn_params_t phy_1m_conn_params = {
.scan_interval = 0x40,
.scan_window = 0x40,
.interval_min = 0x18,
.interval_max = 0x28,
.latency = 0,
.supervision_timeout = 0x1F4,
.min_ce_len = 0,
.max_ce_len = 0,
};
static const esp_ble_conn_params_t phy_2m_conn_params = {
.scan_interval = 0x40,
.scan_window = 0x40,
.interval_min = 0x18,
.interval_max = 0x28,
.latency = 0,
.supervision_timeout = 0x1F4,
.min_ce_len = 0,
.max_ce_len = 0,
};
static const esp_ble_conn_params_t phy_coded_conn_params = {
.scan_interval = 0x40,
.scan_window = 0x40,
.interval_min = 0x18,
.interval_max = 0x28,
.latency = 0,
.supervision_timeout = 0x1F4,
.min_ce_len = 0,
.max_ce_len = 0,
};
struct gattc_profile_inst {
esp_gattc_cb_t gattc_cb;
uint16_t gattc_if;
uint16_t app_id;
uint16_t conn_id;
esp_bd_addr_t remote_bda;
};
static struct gattc_profile_inst gl_profile_tab[PROFILE_NUM] = {
[PROFILE_A_APP_ID] = {
.gattc_cb = NULL,
.gattc_if = ESP_GATT_IF_NONE,
},
};
/**
* @brief Initialize power control features after connection
*/
static void init_power_control(uint16_t conn_handle)
{
if (conn_handle == 0xFFFF) {
ESP_LOGE(TAG, "Invalid connection handle");
return;
}
ESP_LOGI(TAG, "Initializing power control features...");
// Read local transmit power level (LE 1M PHY)
esp_err_t ret = esp_ble_gap_enhanced_read_transmit_power_level(conn_handle, ESP_BLE_CONN_TX_POWER_PHY_1M);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Failed to read local transmit power level, error = 0x%x", ret);
} else {
ESP_LOGI(TAG, "Read local transmit power level request sent");
}
// Read remote transmit power level (LE 1M PHY)
ret = esp_ble_gap_read_remote_transmit_power_level(conn_handle, ESP_BLE_CONN_TX_POWER_PHY_1M);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Failed to read remote transmit power level, error = 0x%x", ret);
} else {
ESP_LOGI(TAG, "Read remote transmit power level request sent");
}
// Enable transmit power reporting (both local and remote)
ret = esp_ble_gap_set_transmit_power_reporting_enable(conn_handle, true, true);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Failed to enable transmit power reporting, error = 0x%x", ret);
} else {
ESP_LOGI(TAG, "Transmit power reporting enabled");
}
// Set path loss reporting parameters
esp_ble_path_loss_rpt_params_t path_loss_params = {
.conn_handle = conn_handle,
.high_threshold = 60, // High threshold in dB
.high_hysteresis = 10, // High hysteresis in dB
.low_threshold = 30, // Low threshold in dB
.low_hysteresis = 10, // Low hysteresis in dB
.min_time_spent = 2, // Minimum time spent in dB
};
ret = esp_ble_gap_set_path_loss_reporting_params(&path_loss_params);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Failed to set path loss reporting parameters, error = 0x%x", ret);
} else {
ESP_LOGI(TAG, "Path loss reporting parameters set");
}
// Enable path loss reporting
ret = esp_ble_gap_set_path_loss_reporting_enable(conn_handle, true);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Failed to enable path loss reporting, error = 0x%x", ret);
} else {
ESP_LOGI(TAG, "Path loss reporting enabled");
}
}
/**
* @brief GATT client event handler
*/
static void gattc_profile_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param)
{
if (param == NULL) {
ESP_LOGE(TAG, "GATTC event handler received NULL param");
return;
}
esp_ble_gattc_cb_param_t *p_data = (esp_ble_gattc_cb_param_t *)param;
switch (event) {
case ESP_GATTC_REG_EVT:
ESP_LOGI(TAG, "GATT client register, status %d, app_id %d, gattc_if %d",
p_data->reg.status, p_data->reg.app_id, gattc_if);
gl_profile_tab[PROFILE_A_APP_ID].gattc_if = gattc_if;
esp_err_t scan_ret = esp_ble_gap_set_ext_scan_params(&ext_scan_params);
if (scan_ret) {
ESP_LOGE(TAG, "set ext scan params error, error code = %x", scan_ret);
}
break;
case ESP_GATTC_CONNECT_EVT:
ESP_LOGI(TAG, "Connected, conn_id %d, remote "ESP_BD_ADDR_STR"",
p_data->connect.conn_id, ESP_BD_ADDR_HEX(p_data->connect.remote_bda));
gl_profile_tab[PROFILE_A_APP_ID].conn_id = p_data->connect.conn_id;
memcpy(gl_profile_tab[PROFILE_A_APP_ID].remote_bda, p_data->connect.remote_bda, sizeof(esp_bd_addr_t));
conn_handle = p_data->connect.conn_id;
// Initialize power control after connection
init_power_control(conn_handle);
break;
case ESP_GATTC_DISCONNECT_EVT:
connect = false;
conn_handle = 0xFFFF;
ESP_LOGI(TAG, "Disconnected, remote "ESP_BD_ADDR_STR", reason 0x%02x",
ESP_BD_ADDR_HEX(p_data->disconnect.remote_bda), p_data->disconnect.reason);
break;
default:
break;
}
}
/**
* @brief GAP event handler
*/
static void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param)
{
if (param == NULL) {
ESP_LOGE(TAG, "GAP event handler received NULL param");
return;
}
uint8_t *adv_name = NULL;
uint8_t adv_name_len = 0;
switch (event) {
case ESP_GAP_BLE_SET_EXT_SCAN_PARAMS_COMPLETE_EVT:
ESP_LOGI(TAG, "Extended scan parameters set, starting scan...");
esp_ble_gap_start_ext_scan(0, 0); // Scan indefinitely
break;
case ESP_GAP_BLE_EXT_SCAN_START_COMPLETE_EVT:
if (param->ext_scan_start.status != ESP_BT_STATUS_SUCCESS) {
ESP_LOGE(TAG, "Extended scan start failed, status %x", param->ext_scan_start.status);
break;
}
ESP_LOGI(TAG, "Extended scan started successfully");
break;
case ESP_GAP_BLE_EXT_ADV_REPORT_EVT: {
adv_name = esp_ble_resolve_adv_data_by_type(param->ext_adv_report.params.adv_data,
param->ext_adv_report.params.adv_data_len,
ESP_BLE_AD_TYPE_NAME_CMPL,
&adv_name_len);
if (adv_name != NULL && adv_name_len > 0) {
ESP_LOGI(TAG, "Found device: "ESP_BD_ADDR_STR", name: %.*s",
ESP_BD_ADDR_HEX(param->ext_adv_report.params.addr), adv_name_len, adv_name);
if (adv_name_len == strlen(REMOTE_DEVICE_NAME) &&
strncmp((char *)adv_name, REMOTE_DEVICE_NAME, adv_name_len) == 0) {
ESP_LOGI(TAG, "Target device found, connecting...");
if (!connect) {
connect = true;
esp_ble_gap_stop_ext_scan();
esp_ble_gatt_creat_conn_params_t creat_conn_params = {0};
memcpy(&creat_conn_params.remote_bda, param->ext_adv_report.params.addr, ESP_BD_ADDR_LEN);
creat_conn_params.remote_addr_type = param->ext_adv_report.params.addr_type;
creat_conn_params.own_addr_type = BLE_ADDR_TYPE_PUBLIC;
creat_conn_params.is_direct = true;
creat_conn_params.is_aux = true;
creat_conn_params.phy_mask = ESP_BLE_PHY_1M_PREF_MASK | ESP_BLE_PHY_2M_PREF_MASK | ESP_BLE_PHY_CODED_PREF_MASK;
creat_conn_params.phy_1m_conn_params = &phy_1m_conn_params;
creat_conn_params.phy_2m_conn_params = &phy_2m_conn_params;
creat_conn_params.phy_coded_conn_params = &phy_coded_conn_params;
esp_ble_gattc_enh_open(gl_profile_tab[PROFILE_A_APP_ID].gattc_if, &creat_conn_params);
}
}
}
break;
}
case ESP_GAP_BLE_EXT_SCAN_STOP_COMPLETE_EVT:
ESP_LOGI(TAG, "Extended scan stopped");
break;
case ESP_GAP_BLE_ENH_READ_TRANS_PWR_LEVEL_EVT:
ESP_LOGI(TAG, "Enhanced read transmit power level: conn_handle=%d, status=%d, phy=%d, cur_tx_pwr=%d, max_tx_pwr=%d",
param->enh_trans_pwr_level_cmpl.conn_handle,
param->enh_trans_pwr_level_cmpl.status,
param->enh_trans_pwr_level_cmpl.phy,
param->enh_trans_pwr_level_cmpl.cur_tx_pwr_level,
param->enh_trans_pwr_level_cmpl.max_tx_pwr_level);
break;
case ESP_GAP_BLE_READ_REMOTE_TRANS_PWR_LEVEL_EVT:
ESP_LOGI(TAG, "Read remote transmit power level complete, status %d", param->read_remote_trans_pwr_level_cmpl.status);
break;
case ESP_GAP_BLE_SET_PATH_LOSS_RPTING_PARAMS_EVT:
ESP_LOGI(TAG, "Set path loss reporting parameters complete, status %d, conn_handle=%d",
param->set_path_loss_rpting_params.status,
param->set_path_loss_rpting_params.conn_handle);
break;
case ESP_GAP_BLE_SET_PATH_LOSS_RPTING_ENABLE_EVT:
ESP_LOGI(TAG, "Set path loss reporting enable complete, status %d, conn_handle=%d",
param->set_path_loss_rpting_enable.status,
param->set_path_loss_rpting_enable.conn_handle);
break;
case ESP_GAP_BLE_SET_TRANS_PWR_RPTING_ENABLE_EVT:
ESP_LOGI(TAG, "Set transmit power reporting enable complete, status %d, conn_handle=%d",
param->set_trans_pwr_rpting_enable.status,
param->set_trans_pwr_rpting_enable.conn_handle);
break;
case ESP_GAP_BLE_PATH_LOSS_THRESHOLD_EVT:
ESP_LOGI(TAG, "Path loss threshold event: conn_handle=%d, cur_path_loss=%d, zone_entered=%d",
param->path_loss_thres_evt.conn_handle,
param->path_loss_thres_evt.cur_path_loss,
param->path_loss_thres_evt.zone_entered);
break;
case ESP_GAP_BLE_TRANS_PWR_RPTING_EVT:
ESP_LOGI(TAG, "Transmit power reporting event: conn_handle=%d, status=%d, reason=%d, phy=%d, power_level=%d, power_level_flag=%d, delta=%d",
param->trans_power_report_evt.conn_handle,
param->trans_power_report_evt.status,
param->trans_power_report_evt.reason,
param->trans_power_report_evt.phy,
param->trans_power_report_evt.tx_power_level,
param->trans_power_report_evt.tx_power_level_flag,
param->trans_power_report_evt.delta);
break;
default:
break;
}
}
void app_main(void)
{
esp_err_t ret;
// Initialize NVS
ret = nvs_flash_init();
if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
ESP_ERROR_CHECK(nvs_flash_erase());
ret = nvs_flash_init();
}
ESP_ERROR_CHECK(ret);
// Initialize Bluetooth Controller
ESP_ERROR_CHECK(esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT));
esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT();
ret = esp_bt_controller_init(&bt_cfg);
if (ret) {
ESP_LOGE(TAG, "Bluetooth controller init failed, error = %x", ret);
return;
}
ret = esp_bt_controller_enable(ESP_BT_MODE_BLE);
if (ret) {
ESP_LOGE(TAG, "Bluetooth controller enable failed, error = %x", ret);
return;
}
// Initialize Bluedroid
esp_bluedroid_config_t cfg = BT_BLUEDROID_INIT_CONFIG_DEFAULT();
ret = esp_bluedroid_init_with_cfg(&cfg);
if (ret) {
ESP_LOGE(TAG, "Bluedroid init failed, error = %x", ret);
return;
}
ret = esp_bluedroid_enable();
if (ret) {
ESP_LOGE(TAG, "Bluedroid enable failed, error = %x", ret);
return;
}
// Register callbacks
ret = esp_ble_gap_register_callback(gap_event_handler);
if (ret) {
ESP_LOGE(TAG, "GAP register callback failed, error = %x", ret);
return;
}
ret = esp_ble_gattc_register_callback(gattc_profile_event_handler);
if (ret) {
ESP_LOGE(TAG, "GATTC register callback failed, error = %x", ret);
return;
}
ret = esp_ble_gattc_app_register(PROFILE_A_APP_ID);
if (ret) {
ESP_LOGE(TAG, "GATTC app register failed, error = %x", ret);
return;
}
ESP_LOGI(TAG, "BLE Power Control Central example started");
}

View File

@@ -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

View File

@@ -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)

View File

@@ -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

View File

@@ -0,0 +1,3 @@
idf_component_register(SRCS "main.c"
PRIV_REQUIRES bt nvs_flash
INCLUDE_DIRS ".")

View File

@@ -0,0 +1,428 @@
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
/****************************************************************************
*
* This demo showcases BLE Power Control feature on Peripheral side.
* It advertises and waits for connection from a central device. After connection,
* it reads remote transmit power level and enables power reporting to monitor
* power consumption.
*
****************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "esp_system.h"
#include "esp_log.h"
#include "nvs_flash.h"
#include "esp_bt.h"
#include "esp_gap_ble_api.h"
#include "esp_gatts_api.h"
#include "esp_bt_defs.h"
#include "esp_bt_main.h"
#include "esp_gatt_common_api.h"
#include "power_control_service.h"
#define TAG "BLE_POWER_CONTROL_PERIPHERAL"
#define DEVICE_NAME "ESP_PWR_CTL_PRH"
#define PROFILE_NUM 1
#define PROFILE_A_APP_ID 0
#define SVC_INST_ID 0
#define EXT_ADV_HANDLE 0
#define NUM_EXT_ADV 1
#define SEM_WAIT_TIMEOUT_MS 5000
#define FUNC_SEND_WAIT_SEM(func, sem) do {\
esp_err_t __err_rc = (func);\
if (__err_rc != ESP_OK) { \
ESP_LOGE(TAG, "%s, message send fail, error = %d", __func__, __err_rc); \
} \
if (xSemaphoreTake(sem, pdMS_TO_TICKS(SEM_WAIT_TIMEOUT_MS)) != pdTRUE) { \
ESP_LOGE(TAG, "%s, semaphore timeout", __func__); \
} \
} while(0);
static SemaphoreHandle_t test_sem = NULL;
static uint16_t conn_handle = 0xFFFF;
uint16_t power_control_handle_table[POWER_CONTROL_IDX_NB];
/* UUIDs */
static const uint16_t primary_service_uuid = ESP_GATT_UUID_PRI_SERVICE;
static const uint16_t character_declaration_uuid = ESP_GATT_UUID_CHAR_DECLARE;
/* Service UUID - must be a variable, not a macro, to take address */
static const uint16_t power_control_service_uuid = BLE_UUID_POWER_CONTROL_SERVICE_VAL;
static const uint16_t power_level_char_uuid = BLE_UUID_POWER_CONTROL_SERVICE_VAL;
/* Characteristic properties */
static const uint8_t char_prop_read = ESP_GATT_CHAR_PROP_BIT_READ;
/* Power Level value */
static uint8_t power_level_value[1] = {0x00};
/* Full Database Description - Used to add attributes into the database */
static const esp_gatts_attr_db_t gatt_db[POWER_CONTROL_IDX_NB] = {
// Service Declaration
[IDX_POWER_CONTROL_SVC] =
{{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&primary_service_uuid, ESP_GATT_PERM_READ,
sizeof(uint16_t), sizeof(power_control_service_uuid), (uint8_t *)&power_control_service_uuid}},
/* Power Level Characteristic Declaration */
[IDX_POWER_LEVEL_CHAR] =
{{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&character_declaration_uuid, ESP_GATT_PERM_READ,
sizeof(uint8_t), sizeof(uint8_t), (uint8_t *)&char_prop_read}},
/* Power Level Characteristic Value */
[IDX_POWER_LEVEL_VAL] =
{{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&power_level_char_uuid, ESP_GATT_PERM_READ,
sizeof(power_level_value), sizeof(power_level_value), (uint8_t *)power_level_value}},
};
// Extended advertising data (includes device name)
// Format: [length][AD type][data...]
// Flag: 0x02 (length) + 0x01 (AD type) + 0x06 (flag value) = 3 bytes
// Name: 0x10 (length) + 0x09 (AD type) + 15 bytes (name) = 17 bytes
static uint8_t raw_ext_adv_data[] = {
0x02, ESP_BLE_AD_TYPE_FLAG, 0x06,
0x10, ESP_BLE_AD_TYPE_NAME_CMPL, 'E','S','P','_','P','W','R','_','C','T','L','_','P','R','H'
};
static esp_ble_gap_ext_adv_params_t ext_adv_params = {
.type = ESP_BLE_GAP_SET_EXT_ADV_PROP_CONNECTABLE,
.interval_min = 0x20, // 32 * 0.625ms = 20ms
.interval_max = 0x40, // 64 * 0.625ms = 40ms
.channel_map = ADV_CHNL_ALL,
.filter_policy = ADV_FILTER_ALLOW_SCAN_ANY_CON_ANY,
.primary_phy = ESP_BLE_GAP_PHY_1M,
.max_skip = 0,
.secondary_phy = ESP_BLE_GAP_PHY_1M,
.sid = 0,
.scan_req_notif = false,
.own_addr_type = BLE_ADDR_TYPE_PUBLIC,
.tx_power = EXT_ADV_TX_PWR_NO_PREFERENCE,
};
static esp_ble_gap_ext_adv_t ext_adv[1] = {
[0] = {EXT_ADV_HANDLE, 0, 0},
};
/**
* @brief Initialize power control features after connection
*/
static void init_power_control(uint16_t conn_handle)
{
if (conn_handle == 0xFFFF) {
ESP_LOGE(TAG, "Invalid connection handle");
return;
}
ESP_LOGI(TAG, "Initializing power control features...");
// Read remote transmit power level (LE 1M PHY)
esp_err_t ret = esp_ble_gap_read_remote_transmit_power_level(conn_handle, ESP_BLE_CONN_TX_POWER_PHY_1M);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Failed to read remote transmit power level, error = 0x%x", ret);
} else {
ESP_LOGI(TAG, "Read remote transmit power level request sent");
}
// Enable transmit power reporting (both local and remote)
ret = esp_ble_gap_set_transmit_power_reporting_enable(conn_handle, true, true);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Failed to enable transmit power reporting, error = 0x%x", ret);
} else {
ESP_LOGI(TAG, "Transmit power reporting enabled");
}
}
/**
* @brief GAP event handler
*/
static void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param)
{
if (param == NULL) {
ESP_LOGE(TAG, "GAP event handler received NULL param");
return;
}
switch (event) {
case ESP_GAP_BLE_EXT_ADV_SET_PARAMS_COMPLETE_EVT:
xSemaphoreGive(test_sem);
ESP_LOGI(TAG, "Extended advertising params set, status %d, instance %d",
param->ext_adv_set_params.status, param->ext_adv_set_params.instance);
break;
case ESP_GAP_BLE_EXT_ADV_DATA_SET_COMPLETE_EVT:
xSemaphoreGive(test_sem);
ESP_LOGI(TAG, "Extended advertising data set, status %d, instance %d",
param->ext_adv_data_set.status, param->ext_adv_data_set.instance);
break;
case ESP_GAP_BLE_EXT_ADV_START_COMPLETE_EVT:
xSemaphoreGive(test_sem);
if (param->ext_adv_start.status != ESP_BT_STATUS_SUCCESS) {
ESP_LOGE(TAG, "Extended advertising start failed, status %d", param->ext_adv_start.status);
break;
}
ESP_LOGI(TAG, "Extended advertising start successfully, instance num %d",
param->ext_adv_start.instance_num);
break;
case ESP_GAP_BLE_EXT_ADV_STOP_COMPLETE_EVT:
if (param->ext_adv_stop.status != ESP_BT_STATUS_SUCCESS) {
ESP_LOGE(TAG, "Extended advertising stop failed, status %d", param->ext_adv_stop.status);
break;
}
ESP_LOGI(TAG, "Extended advertising stop successfully");
break;
case ESP_GAP_BLE_UPDATE_CONN_PARAMS_EVT:
ESP_LOGI(TAG, "Connection parameters updated: min_int=%d, max_int=%d, conn_int=%d, latency=%d, timeout=%d",
param->update_conn_params.min_int,
param->update_conn_params.max_int,
param->update_conn_params.conn_int,
param->update_conn_params.latency,
param->update_conn_params.timeout);
break;
case ESP_GAP_BLE_READ_REMOTE_TRANS_PWR_LEVEL_EVT:
ESP_LOGI(TAG, "Read remote transmit power level complete, status %d", param->read_remote_trans_pwr_level_cmpl.status);
break;
case ESP_GAP_BLE_SET_TRANS_PWR_RPTING_ENABLE_EVT:
ESP_LOGI(TAG, "Set transmit power reporting enable complete, status %d, conn_handle=%d",
param->set_trans_pwr_rpting_enable.status,
param->set_trans_pwr_rpting_enable.conn_handle);
break;
case ESP_GAP_BLE_TRANS_PWR_RPTING_EVT:
ESP_LOGI(TAG, "Transmit power reporting event: conn_handle=%d, status=%d, reason=%d, phy=%d, power_level=%d, power_level_flag=%d, delta=%d",
param->trans_power_report_evt.conn_handle,
param->trans_power_report_evt.status,
param->trans_power_report_evt.reason,
param->trans_power_report_evt.phy,
param->trans_power_report_evt.tx_power_level,
param->trans_power_report_evt.tx_power_level_flag,
param->trans_power_report_evt.delta);
break;
default:
break;
}
}
struct gatts_profile_inst {
esp_gatts_cb_t gatts_cb;
uint16_t gatts_if;
uint16_t app_id;
uint16_t conn_id;
};
static void gatts_profile_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param);
static struct gatts_profile_inst gl_profile_tab[PROFILE_NUM] = {
[PROFILE_A_APP_ID] = {
.gatts_cb = gatts_profile_event_handler,
.gatts_if = ESP_GATT_IF_NONE,
},
};
/**
* @brief GATTS profile event handler
*/
static void gatts_profile_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param)
{
if (param == NULL) {
ESP_LOGE(TAG, "GATTS event handler received NULL param");
return;
}
switch (event) {
case ESP_GATTS_REG_EVT: {
ESP_LOGI(TAG, "GATT server register, status %d, app_id %d, gatts_if %d",
param->reg.status, param->reg.app_id, gatts_if);
esp_ble_gap_set_device_name(DEVICE_NAME);
// Create attribute table
esp_err_t create_attr_ret = esp_ble_gatts_create_attr_tab(gatt_db, gatts_if, POWER_CONTROL_IDX_NB, SVC_INST_ID);
if (create_attr_ret) {
ESP_LOGE(TAG, "create attr table failed, error code = %x", create_attr_ret);
}
break;
}
case ESP_GATTS_CREAT_ATTR_TAB_EVT: {
if (param->add_attr_tab.status != ESP_GATT_OK) {
ESP_LOGE(TAG, "create attribute table failed, error code=0x%x", param->add_attr_tab.status);
} else if (param->add_attr_tab.num_handle != POWER_CONTROL_IDX_NB) {
ESP_LOGE(TAG, "create attribute table abnormally, num_handle (%d) doesn't equal to POWER_CONTROL_IDX_NB(%d)",
param->add_attr_tab.num_handle, POWER_CONTROL_IDX_NB);
} else {
ESP_LOGI(TAG, "create attribute table successfully, the number handle = %d", param->add_attr_tab.num_handle);
// Validate handles pointer before memcpy
if (param->add_attr_tab.handles) {
memcpy(power_control_handle_table, param->add_attr_tab.handles, sizeof(power_control_handle_table));
esp_ble_gatts_start_service(power_control_handle_table[IDX_POWER_CONTROL_SVC]);
} else {
ESP_LOGE(TAG, "Invalid handles pointer");
}
}
break;
}
case ESP_GATTS_START_EVT:
ESP_LOGI(TAG, "Service start, status %d, service_handle %d",
param->start.status, param->start.service_handle);
break;
case ESP_GATTS_CONNECT_EVT: {
ESP_LOGI(TAG, "Connected, conn_id %u, remote "ESP_BD_ADDR_STR"",
param->connect.conn_id, ESP_BD_ADDR_HEX(param->connect.remote_bda));
gl_profile_tab[PROFILE_A_APP_ID].conn_id = param->connect.conn_id;
conn_handle = param->connect.conn_id;
esp_ble_conn_update_params_t conn_params = {0};
memcpy(conn_params.bda, param->connect.remote_bda, sizeof(esp_bd_addr_t));
conn_params.latency = 0;
conn_params.max_int = 0x20;
conn_params.min_int = 0x10;
conn_params.timeout = 400;
esp_ble_gap_update_conn_params(&conn_params);
// Stop extended advertising
uint8_t adv_handle = EXT_ADV_HANDLE;
esp_ble_gap_ext_adv_stop(1, &adv_handle);
// Initialize power control after connection
init_power_control(conn_handle);
break;
}
case ESP_GATTS_DISCONNECT_EVT:
ESP_LOGI(TAG, "Disconnected, remote "ESP_BD_ADDR_STR", reason 0x%02x",
ESP_BD_ADDR_HEX(param->disconnect.remote_bda), param->disconnect.reason);
conn_handle = 0xFFFF;
// Restart extended advertising
FUNC_SEND_WAIT_SEM(esp_ble_gap_ext_adv_start(NUM_EXT_ADV, ext_adv), test_sem);
break;
case ESP_GATTS_READ_EVT:
ESP_LOGI(TAG, "Read event, handle %d", param->read.handle);
// Auto response is enabled, so no need to send response manually
break;
case ESP_GATTS_WRITE_EVT:
ESP_LOGI(TAG, "Write event, handle %d, len %d", param->write.handle, param->write.len);
// Auto response is enabled, so no need to send response manually
break;
case ESP_GATTS_MTU_EVT:
ESP_LOGI(TAG, "MTU exchange, MTU %d", param->mtu.mtu);
break;
default:
break;
}
}
static void esp_gatts_cb(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param)
{
if (event == ESP_GATTS_REG_EVT) {
if (param->reg.status == ESP_GATT_OK) {
gl_profile_tab[param->reg.app_id].gatts_if = gatts_if;
} else {
ESP_LOGI(TAG, "reg app failed, app_id %04x, status %d",
param->reg.app_id, param->reg.status);
return;
}
}
do {
int idx;
for (idx = 0; idx < PROFILE_NUM; idx++) {
if (gatts_if == ESP_GATT_IF_NONE ||
gatts_if == gl_profile_tab[idx].gatts_if) {
if (gl_profile_tab[idx].gatts_cb) {
gl_profile_tab[idx].gatts_cb(event, gatts_if, param);
}
}
}
} while (0);
}
void app_main(void)
{
esp_err_t ret;
// Initialize NVS
ret = nvs_flash_init();
if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
ESP_ERROR_CHECK(nvs_flash_erase());
ret = nvs_flash_init();
}
ESP_ERROR_CHECK(ret);
// Initialize Bluetooth Controller
ESP_ERROR_CHECK(esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT));
esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT();
ret = esp_bt_controller_init(&bt_cfg);
if (ret) {
ESP_LOGE(TAG, "Bluetooth controller init failed, error = %x", ret);
return;
}
ret = esp_bt_controller_enable(ESP_BT_MODE_BLE);
if (ret) {
ESP_LOGE(TAG, "Bluetooth controller enable failed, error = %x", ret);
return;
}
// Initialize Bluedroid
esp_bluedroid_config_t cfg = BT_BLUEDROID_INIT_CONFIG_DEFAULT();
ret = esp_bluedroid_init_with_cfg(&cfg);
if (ret) {
ESP_LOGE(TAG, "Bluedroid init failed, error = %x", ret);
return;
}
ret = esp_bluedroid_enable();
if (ret) {
ESP_LOGE(TAG, "Bluedroid enable failed, error = %x", ret);
return;
}
// Register callbacks
ret = esp_ble_gap_register_callback(gap_event_handler);
if (ret) {
ESP_LOGE(TAG, "GAP register callback failed, error = %x", ret);
return;
}
ret = esp_ble_gatts_register_callback(esp_gatts_cb);
if (ret) {
ESP_LOGE(TAG, "gatts register error, error code = %x", ret);
return;
}
ret = esp_ble_gatts_app_register(PROFILE_A_APP_ID);
if (ret) {
ESP_LOGE(TAG, "gatts app register error, error code = %x", ret);
return;
}
// Create semaphore for extended advertising
test_sem = xSemaphoreCreateBinary();
// Start extended advertising
esp_bd_addr_t addr;
esp_ble_gap_addr_create_static(addr);
ESP_LOGI(TAG, "Device Address: "ESP_BD_ADDR_STR"", ESP_BD_ADDR_HEX(addr));
// Set extended advertising parameters
FUNC_SEND_WAIT_SEM(esp_ble_gap_ext_adv_set_params(EXT_ADV_HANDLE, &ext_adv_params), test_sem);
// Set extended advertising data
FUNC_SEND_WAIT_SEM(esp_ble_gap_config_ext_adv_data_raw(EXT_ADV_HANDLE,
sizeof(raw_ext_adv_data),
raw_ext_adv_data), test_sem);
// Start extended advertising
FUNC_SEND_WAIT_SEM(esp_ble_gap_ext_adv_start(NUM_EXT_ADV, ext_adv), test_sem);
ESP_LOGI(TAG, "BLE Power Control Peripheral example started");
}

View File

@@ -0,0 +1,25 @@
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
#ifndef POWER_CONTROL_SERVICE_H
#define POWER_CONTROL_SERVICE_H
#include <stdint.h>
/* Power Control Service UUIDs */
#define BLE_UUID_POWER_CONTROL_SERVICE_VAL 0xFF01
/* Attributes State Machine */
enum {
IDX_POWER_CONTROL_SVC,
/* Power Level Characteristic */
IDX_POWER_LEVEL_CHAR,
IDX_POWER_LEVEL_VAL,
POWER_CONTROL_IDX_NB,
};
#endif /* POWER_CONTROL_SERVICE_H */

View File

@@ -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