feat(examples/bluedroid): add BLE ACL latency measurement example

(cherry picked from commit 934970a06fc12973e2a1ec6381c9f78e5da6dbbb)

Co-authored-by: zhanghaipeng <zhanghaipeng@espressif.com>
This commit is contained in:
Zhang Hai Peng
2025-12-30 11:53:38 +08:00
committed by BOT
parent 9ef7842daa
commit 086ebb7ccb
16 changed files with 1582 additions and 0 deletions
+18
View File
@@ -27,6 +27,24 @@ examples/bluetooth/bluedroid/ble:
depends_filepatterns:
- examples/bluetooth/bluedroid/ble/pytest_ble_test.py
examples/bluetooth/bluedroid/ble/ble_acl_latency/cent:
<<: *bt_default_depends
disable:
- if: SOC_BLE_SUPPORTED != 1
enable:
- if: IDF_TARGET in ["esp32", "esp32c2", "esp32c3", "esp32c6", "esp32h2", "esp32s3"]
temporary: true
reason: only enable build jobs for tested targets
examples/bluetooth/bluedroid/ble/ble_acl_latency/periph:
<<: *bt_default_depends
disable:
- if: SOC_BLE_SUPPORTED != 1
enable:
- if: IDF_TARGET in ["esp32", "esp32c2", "esp32c3", "esp32c6", "esp32h2", "esp32s3"]
temporary: true
reason: only enable build jobs for tested targets
examples/bluetooth/bluedroid/ble/ble_hid_device_demo:
<<: *bt_default_depends
disable:
@@ -0,0 +1,6 @@
# 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)
project(ble_acl_latency_cent)
@@ -0,0 +1,190 @@
| Supported Targets | ESP32 | ESP32-C2 | ESP32-C3 | ESP32-C6 | ESP32-H2 | ESP32-S3 |
| ----------------- | ----- | -------- | -------- | -------- | -------- | -------- |
# ESP-IDF BLE ACL Latency Test - Central
This example demonstrates how to measure BLE ACL (Asynchronous Connection-Less) link latency. The central device scans for the peripheral, connects to it, and then performs a latency test by sending packets and measuring the round-trip time (RTT).
This demo should be used together with the `periph` (peripheral) example.
## How It Works
1. The central scans for a device named `ESP_ACL_LATENCY`
2. Upon finding the peripheral, it connects and performs MTU exchange
3. After enabling notifications, the central sends 100 test packets (242 bytes each)
4. For each packet, the central records the send time
5. When the echoed packet is received via notification, it calculates RTT
6. Finally, statistics (average, min, max latency, packet loss) are printed
## Test Flow Diagram
```
┌──────────────┐ ┌──────────────┐
│ Central │ │ Peripheral │
│ (GATT Client)│ │ (GATT Server)│
└──────┬───────┘ └──────┬───────┘
│ │
│ ──────────── Connection Setup ─────────── │
│ │
│ 1. Scan for "ESP_ACL_LATENCY" │
│ ─────────────────────────────────────────> │ Advertising
│ │
│ 2. Connect Request │
│ ─────────────────────────────────────────> │
│ │
│ 3. Connection Established │
│ <─────────────────────────────────────────> │
│ │
│ 4. MTU Exchange (517 bytes) │
│ <─────────────────────────────────────────> │
│ │
│ 5. Connection Parameter Update (7.5ms) │
│ <─────────────────────────────────────────> │
│ │
│ 6. Service Discovery │
│ ─────────────────────────────────────────> │
│ │
│ 7. Enable Notification (CCCD) │
│ ─────────────────────────────────────────> │
│ │
│ ──────────── Latency Test ───────────── │
│ │
│ 8. Write Test Packet [SEQ=0] ──┐ │
│ Record send_time t1│ │
│ ─────────────────────────────────────────> │
│ │ │ Echo back
│ 9. Notification [SEQ=0] │ │ via Notify
│ Record recv_time t2│ │
│ <───────────────────────────────────────── │
│ │ │
│ RTT = t2 - t1 ──┘ │
│ Latency = RTT / 2 │
│ │
│ ... Repeat for SEQ 1-99 ... │
│ │
│ 10. Print Statistics │
│ - Average/Min/Max Latency │
│ - Packet Loss Rate │
│ │
┌──────┴───────┐ ┌──────┴───────┐
│ Central │ │ Peripheral │
└──────────────┘ └──────────────┘
```
## Test Configuration
The following parameters can be modified in `gattc_latency.h`:
| Parameter | Default Value | Description |
|------------------------|---------------|------------------------------------|
| TEST_PACKET_COUNT | 100 | Number of test packets to send |
| TEST_PACKET_INTERVAL_MS| 20 | Interval between packets (ms) |
## How to Use Example
### Hardware Required
* Two development boards with supported SoC (e.g., ESP32-C3-DevKitC, ESP32-DevKitC, etc.)
* Two USB cables for power supply and programming
See [Development Boards](https://www.espressif.com/en/products/devkits) for more information.
### Configure the Project
Before project configuration and build, be sure to set the correct chip target using:
```bash
idf.py set-target <chip_name>
```
### Build and Flash
1. First, flash the `periph` example to one board:
```bash
cd ../periph
idf.py -p PORT1 flash
```
2. Then, flash this `cent` example to another board:
```bash
cd ../cent
idf.py -p PORT2 flash
```
### Monitor
Run `idf.py -p PORT monitor` to view the serial output of the central device.
(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 (362) BLE_ACL_LATENCY_CENT: BLE ACL Latency Test - Central
I (372) phy_init: phy_version 310,dde1ba9,Jun 4 2024,16:38:11
I (432) BLE_ACL_LATENCY_CENT: REG_EVT
I (442) BLE_ACL_LATENCY_CENT: scan start success
I (442) BLE_ACL_LATENCY_CENT: Central initialized, scanning for peripheral...
I (622) BLE_ACL_LATENCY_CENT: searched device ESP_ACL_LATENCY
I (622) BLE_ACL_LATENCY_CENT: connect to the remote device.
I (622) BLE_ACL_LATENCY_CENT: stop scan successfully
I (892) BLE_ACL_LATENCY_CENT: ESP_GATTC_CONNECT_EVT conn_id 0, if 4
I (892) BLE_ACL_LATENCY_CENT: REMOTE BDA: 58:cf:79:ea:77:30
I (892) BLE_ACL_LATENCY_CENT: open success
I (1042) BLE_ACL_LATENCY_CENT: update connection params status = 0, min_int = 6, max_int = 6,conn_int = 6,latency = 0, timeout = 500
I (1112) BLE_ACL_LATENCY_CENT: discover service complete conn_id 0
I (1112) BLE_ACL_LATENCY_CENT: service found
I (1122) BLE_ACL_LATENCY_CENT: ESP_GATTC_CFG_MTU_EVT, Status 0, MTU 517, conn_id 0
I (1132) BLE_ACL_LATENCY_CENT: ESP_GATTC_REG_FOR_NOTIFY_EVT
I (1172) BLE_ACL_LATENCY_CENT: write descr success
I (1172) BLE_ACL_LATENCY_CENT: Initializing latency test...
I (1172) LATENCY_TEST: Latency test initialized, conn_id=0, handle=42
I (2182) BLE_ACL_LATENCY_CENT: Starting latency test...
I (2182) LATENCY_TEST: ====================================
I (2182) LATENCY_TEST: Starting latency test...
I (2182) LATENCY_TEST: Packets: 100, Interval: 20 ms
I (2182) LATENCY_TEST: ====================================
SEQ= 0, RTT= 20040 us, Latency= 10020 us (10.02 ms)
SEQ= 1, RTT= 18341 us, Latency= 9170 us (9.17 ms)
SEQ= 2, RTT= 13339 us, Latency= 6669 us (6.67 ms)
...
SEQ= 99, RTT= 15835 us, Latency= 7917 us (7.92 ms)
I (4192) LATENCY_TEST: All packets sent, waiting for responses...
I (6192) LATENCY_TEST:
I (6192) LATENCY_TEST: ========================================
I (6192) LATENCY_TEST: Test Results Summary
I (6192) LATENCY_TEST: ========================================
I (6192) LATENCY_TEST: Total packets: 100
I (6202) LATENCY_TEST: Received: 100
I (6202) LATENCY_TEST: Lost: 0 (0.0%)
I (6202) LATENCY_TEST: ----------------------------------------
I (6212) LATENCY_TEST: Average latency: 7929 us (7.93 ms)
I (6222) LATENCY_TEST: Min latency: 6667 us (6.67 ms)
I (6222) LATENCY_TEST: Max latency: 10020 us (10.02 ms)
I (6232) LATENCY_TEST: ========================================
```
## Connection Parameters
The example uses optimized connection parameters for low latency:
| Parameter | Value | Description |
|---------------------|----------|--------------------------------|
| Connection Interval | 7.5 ms | Minimum allowed by BLE spec |
| Slave Latency | 0 | No skipped connection events |
| Supervision Timeout | 4000 ms | Connection timeout |
| MTU | 517 | Maximum ATT MTU |
## Understanding the Results
- **RTT (Round-Trip Time)**: Time from sending a packet until receiving the echo
- **Latency**: Estimated one-way delay (RTT / 2)
- **Expected Results**: With 7.5ms connection interval, typical latency is 6-10ms
## Troubleshooting
For any technical queries, please open an [issue](https://github.com/espressif/esp-idf/issues) on GitHub. We will get back to you soon.
@@ -0,0 +1,2 @@
idf_component_register(SRCS "gattc_latency_demo.c" "latency_test.c"
INCLUDE_DIRS ".")
@@ -0,0 +1,56 @@
/*
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
#ifndef H_GATTC_LATENCY_
#define H_GATTC_LATENCY_
#include "esp_gattc_api.h"
#include "esp_gap_ble_api.h"
#ifdef __cplusplus
extern "C" {
#endif
/* Service and Characteristic UUIDs - must match peripheral */
#define REMOTE_SERVICE_UUID 0x1234
#define REMOTE_CHAR_UUID 0x5678
/* Profile ID */
#define PROFILE_APP_ID 0
#define INVALID_HANDLE 0
/* Remote device name */
#define REMOTE_DEVICE_NAME "ESP_ACL_LATENCY"
/* Test configuration */
#define TEST_PACKET_COUNT 100
#define TEST_PACKET_INTERVAL_MS 20
/* Test packet structure */
typedef struct {
uint16_t seq_num;
uint8_t payload[240];
} __attribute__((packed)) test_packet_t;
/* Latency record */
typedef struct {
uint16_t seq;
int64_t send_time_us;
int64_t recv_time_us;
bool received;
} latency_record_t;
/* Latency test functions */
void latency_test_init(uint16_t conn_id, uint16_t char_handle);
void latency_test_start(void);
void latency_test_handle_notify(uint8_t *data, uint16_t len);
void latency_test_print_results(void);
#ifdef __cplusplus
}
#endif
#endif /* H_GATTC_LATENCY_ */
@@ -0,0 +1,480 @@
/*
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
#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"
#include "gattc_latency.h"
#define GATTC_TAG "BLE_ACL_LATENCY_CENT"
#define PROFILE_NUM 1
#define PROFILE_APP_ID 0
static bool connect = false;
static bool get_server = false;
static esp_gattc_char_elem_t *char_elem_result = NULL;
static esp_gattc_descr_elem_t *descr_elem_result = NULL;
/* 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);
/* Extern function */
extern void latency_test_set_gattc_if(esp_gatt_if_t gattc_if);
static esp_bt_uuid_t remote_filter_service_uuid = {
.len = ESP_UUID_LEN_16,
.uuid = {.uuid16 = REMOTE_SERVICE_UUID,},
};
static esp_bt_uuid_t remote_filter_char_uuid = {
.len = ESP_UUID_LEN_16,
.uuid = {.uuid16 = REMOTE_CHAR_UUID,},
};
static esp_bt_uuid_t notify_descr_uuid = {
.len = ESP_UUID_LEN_16,
.uuid = {.uuid16 = ESP_GATT_UUID_CHAR_CLIENT_CONFIG,},
};
static esp_ble_scan_params_t ble_scan_params = {
.scan_type = BLE_SCAN_TYPE_ACTIVE,
.own_addr_type = BLE_ADDR_TYPE_PUBLIC,
.scan_filter_policy = BLE_SCAN_FILTER_ALLOW_ALL,
.scan_interval = ESP_BLE_GAP_SCAN_ITVL_MS(50), // 50ms
.scan_window = ESP_BLE_GAP_SCAN_WIN_MS(30), // 30ms
.scan_duplicate = BLE_SCAN_DUPLICATE_DISABLE
};
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 char_handle;
esp_bd_addr_t remote_bda;
};
static struct gattc_profile_inst gl_profile_tab[PROFILE_NUM] = {
[PROFILE_APP_ID] = {
.gattc_cb = gattc_profile_event_handler,
.gattc_if = ESP_GATT_IF_NONE,
},
};
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(GATTC_TAG, "REG_EVT");
gl_profile_tab[PROFILE_APP_ID].gattc_if = gattc_if;
esp_err_t scan_ret = esp_ble_gap_set_scan_params(&ble_scan_params);
if (scan_ret){
ESP_LOGE(GATTC_TAG, "set scan params error, error code = %x", scan_ret);
}
break;
case ESP_GATTC_CONNECT_EVT:{
ESP_LOGI(GATTC_TAG, "ESP_GATTC_CONNECT_EVT conn_id %d, if %d", p_data->connect.conn_id, gattc_if);
gl_profile_tab[PROFILE_APP_ID].conn_id = p_data->connect.conn_id;
memcpy(gl_profile_tab[PROFILE_APP_ID].remote_bda, p_data->connect.remote_bda, sizeof(esp_bd_addr_t));
ESP_LOGI(GATTC_TAG, "REMOTE BDA: %02x:%02x:%02x:%02x:%02x:%02x",
p_data->connect.remote_bda[0], p_data->connect.remote_bda[1],
p_data->connect.remote_bda[2], p_data->connect.remote_bda[3],
p_data->connect.remote_bda[4], p_data->connect.remote_bda[5]);
/* Update connection parameters to minimum interval (7.5ms) for lower latency */
esp_ble_conn_update_params_t conn_params = {0};
memcpy(conn_params.bda, p_data->connect.remote_bda, sizeof(esp_bd_addr_t));
conn_params.min_int = ESP_BLE_GAP_CONN_ITVL_MS(7.5); // 7.5ms - minimum allowed
conn_params.max_int = ESP_BLE_GAP_CONN_ITVL_MS(7.5); // 7.5ms - minimum allowed
conn_params.latency = 0;
conn_params.timeout = ESP_BLE_GAP_SUPERVISION_TIMEOUT_MS(4000); // 4s
esp_ble_gap_update_conn_params(&conn_params);
esp_err_t mtu_ret = esp_ble_gattc_send_mtu_req (gattc_if, p_data->connect.conn_id);
if (mtu_ret){
ESP_LOGE(GATTC_TAG, "config MTU error, error code = %x", mtu_ret);
}
break;
}
case ESP_GATTC_OPEN_EVT:
if (param->open.status != ESP_GATT_OK){
ESP_LOGE(GATTC_TAG, "open failed, status %d", p_data->open.status);
break;
}
ESP_LOGI(GATTC_TAG, "open success");
break;
case ESP_GATTC_DIS_SRVC_CMPL_EVT:
if (param->dis_srvc_cmpl.status != ESP_GATT_OK){
ESP_LOGE(GATTC_TAG, "discover service failed, status %d", param->dis_srvc_cmpl.status);
break;
}
ESP_LOGI(GATTC_TAG, "discover service 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_CFG_MTU_EVT:
if (param->cfg_mtu.status != ESP_GATT_OK){
ESP_LOGE(GATTC_TAG,"config mtu failed, error status = %x", param->cfg_mtu.status);
}
ESP_LOGI(GATTC_TAG, "ESP_GATTC_CFG_MTU_EVT, Status %d, MTU %d, conn_id %d", param->cfg_mtu.status, param->cfg_mtu.mtu, param->cfg_mtu.conn_id);
break;
case ESP_GATTC_SEARCH_RES_EVT: {
ESP_LOGI(GATTC_TAG, "SEARCH RES: conn_id = %x is primary service %d", p_data->search_res.conn_id, p_data->search_res.is_primary);
ESP_LOGI(GATTC_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(GATTC_TAG, "service found");
get_server = true;
gl_profile_tab[PROFILE_APP_ID].service_start_handle = p_data->search_res.start_handle;
gl_profile_tab[PROFILE_APP_ID].service_end_handle = p_data->search_res.end_handle;
ESP_LOGI(GATTC_TAG, "UUID16: %x", 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(GATTC_TAG, "search service failed, error status = %x", p_data->search_cmpl.status);
break;
}
if(p_data->search_cmpl.searched_service_source == ESP_GATT_SERVICE_FROM_REMOTE_DEVICE) {
ESP_LOGI(GATTC_TAG, "Get service information from remote device");
} else if (p_data->search_cmpl.searched_service_source == ESP_GATT_SERVICE_FROM_NVS_FLASH) {
ESP_LOGI(GATTC_TAG, "Get service information from flash");
} else {
ESP_LOGI(GATTC_TAG, "unknown service source");
}
ESP_LOGI(GATTC_TAG, "ESP_GATTC_SEARCH_CMPL_EVT");
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_APP_ID].service_start_handle,
gl_profile_tab[PROFILE_APP_ID].service_end_handle,
INVALID_HANDLE,
&count);
if (status != ESP_GATT_OK){
ESP_LOGE(GATTC_TAG, "esp_ble_gattc_get_attr_count error");
break;
}
if (count > 0){
char_elem_result = (esp_gattc_char_elem_t *)malloc(sizeof(esp_gattc_char_elem_t) * count);
if (!char_elem_result){
ESP_LOGE(GATTC_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_APP_ID].service_start_handle,
gl_profile_tab[PROFILE_APP_ID].service_end_handle,
remote_filter_char_uuid,
char_elem_result,
&count);
if (status != ESP_GATT_OK){
ESP_LOGE(GATTC_TAG, "esp_ble_gattc_get_char_by_uuid error");
free(char_elem_result);
char_elem_result = NULL;
break;
}
if (count > 0 && (char_elem_result[0].properties & ESP_GATT_CHAR_PROP_BIT_NOTIFY)){
gl_profile_tab[PROFILE_APP_ID].char_handle = char_elem_result[0].char_handle;
esp_ble_gattc_register_for_notify (gattc_if, gl_profile_tab[PROFILE_APP_ID].remote_bda, char_elem_result[0].char_handle);
}
}
free(char_elem_result);
char_elem_result = NULL;
}else{
ESP_LOGE(GATTC_TAG, "no char found");
}
}
break;
case ESP_GATTC_REG_FOR_NOTIFY_EVT: {
ESP_LOGI(GATTC_TAG, "ESP_GATTC_REG_FOR_NOTIFY_EVT");
if (p_data->reg_for_notify.status != ESP_GATT_OK){
ESP_LOGE(GATTC_TAG, "REG FOR NOTIFY failed: error status = %d", p_data->reg_for_notify.status);
}else{
uint16_t count = 0;
uint16_t notify_en = 1;
esp_gatt_status_t ret_status = esp_ble_gattc_get_attr_count( gattc_if,
gl_profile_tab[PROFILE_APP_ID].conn_id,
ESP_GATT_DB_DESCRIPTOR,
gl_profile_tab[PROFILE_APP_ID].service_start_handle,
gl_profile_tab[PROFILE_APP_ID].service_end_handle,
gl_profile_tab[PROFILE_APP_ID].char_handle,
&count);
if (ret_status != ESP_GATT_OK){
ESP_LOGE(GATTC_TAG, "esp_ble_gattc_get_attr_count error");
}
if (count > 0){
descr_elem_result = malloc(sizeof(esp_gattc_descr_elem_t) * count);
if (!descr_elem_result){
ESP_LOGE(GATTC_TAG, "malloc error, gattc no mem");
}else{
ret_status = esp_ble_gattc_get_descr_by_char_handle( gattc_if,
gl_profile_tab[PROFILE_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(GATTC_TAG, "esp_ble_gattc_get_descr_by_char_handle error");
}
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){
ret_status = esp_ble_gattc_write_char_descr( gattc_if,
gl_profile_tab[PROFILE_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(GATTC_TAG, "esp_ble_gattc_write_char_descr error");
}
free(descr_elem_result);
descr_elem_result = NULL;
}
}
else{
ESP_LOGE(GATTC_TAG, "decsr not found");
}
}
break;
}
case ESP_GATTC_NOTIFY_EVT:
if (p_data->notify.is_notify){
ESP_LOGD(GATTC_TAG, "ESP_GATTC_NOTIFY_EVT, receive notify value:");
}else{
ESP_LOGD(GATTC_TAG, "ESP_GATTC_NOTIFY_EVT, receive indicate value:");
}
ESP_LOGD(GATTC_TAG, "Received %d bytes of data", p_data->notify.value_len);
/* Handle latency test notification */
latency_test_handle_notify(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(GATTC_TAG, "write descr failed, error status = %x", p_data->write.status);
break;
}
ESP_LOGI(GATTC_TAG, "write descr success ");
/* After enabling notification, initialize and start latency test */
ESP_LOGI(GATTC_TAG, "Initializing latency test...");
latency_test_init(gl_profile_tab[PROFILE_APP_ID].conn_id, gl_profile_tab[PROFILE_APP_ID].char_handle);
latency_test_set_gattc_if(gl_profile_tab[PROFILE_APP_ID].gattc_if);
vTaskDelay(pdMS_TO_TICKS(1000));
ESP_LOGI(GATTC_TAG, "Starting latency test...");
latency_test_start();
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(GATTC_TAG, "ESP_GATTC_SRVC_CHG_EVT, bd_addr: %02x:%02x:%02x:%02x:%02x:%02x",
bda[0], bda[1], bda[2], bda[3], bda[4], bda[5]);
break;
}
case ESP_GATTC_WRITE_CHAR_EVT:
if (p_data->write.status != ESP_GATT_OK){
ESP_LOGE(GATTC_TAG, "write char failed, error status = %x", p_data->write.status);
break;
}
ESP_LOGD(GATTC_TAG, "write char success ");
break;
case ESP_GATTC_DISCONNECT_EVT:
connect = false;
get_server = false;
ESP_LOGI(GATTC_TAG, "ESP_GATTC_DISCONNECT_EVT, reason = %d", p_data->disconnect.reason);
break;
default:
break;
}
}
static void esp_gap_cb(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param)
{
uint8_t *adv_name = NULL;
uint8_t adv_name_len = 0;
switch (event) {
case ESP_GAP_BLE_SCAN_PARAM_SET_COMPLETE_EVT: {
uint32_t duration = 30;
esp_ble_gap_start_scanning(duration);
break;
}
case ESP_GAP_BLE_SCAN_START_COMPLETE_EVT:
if (param->scan_start_cmpl.status != ESP_BT_STATUS_SUCCESS) {
ESP_LOGE(GATTC_TAG, "scan start failed, error status = %x", param->scan_start_cmpl.status);
break;
}
ESP_LOGI(GATTC_TAG, "scan start success");
break;
case ESP_GAP_BLE_SCAN_RESULT_EVT: {
esp_ble_gap_cb_param_t *scan_result = (esp_ble_gap_cb_param_t *)param;
switch (scan_result->scan_rst.search_evt) {
case ESP_GAP_SEARCH_INQ_RES_EVT:
adv_name = esp_ble_resolve_adv_data_by_type(scan_result->scan_rst.ble_adv,
scan_result->scan_rst.adv_data_len,
ESP_BLE_AD_TYPE_NAME_CMPL, &adv_name_len);
if (adv_name != NULL) {
if (strlen(REMOTE_DEVICE_NAME) == adv_name_len && strncmp((char *)adv_name, REMOTE_DEVICE_NAME, adv_name_len) == 0) {
ESP_LOGI(GATTC_TAG, "searched device %s", REMOTE_DEVICE_NAME);
if (connect == false) {
connect = true;
ESP_LOGI(GATTC_TAG, "connect to the remote device.");
esp_ble_gap_stop_scanning();
esp_ble_gattc_open(gl_profile_tab[PROFILE_APP_ID].gattc_if, scan_result->scan_rst.bda, scan_result->scan_rst.ble_addr_type, true);
}
}
}
break;
case ESP_GAP_SEARCH_INQ_CMPL_EVT:
break;
default:
break;
}
break;
}
case ESP_GAP_BLE_SCAN_STOP_COMPLETE_EVT:
if (param->scan_stop_cmpl.status != ESP_BT_STATUS_SUCCESS){
ESP_LOGE(GATTC_TAG, "scan stop failed, error status = %x", param->scan_stop_cmpl.status);
break;
}
ESP_LOGI(GATTC_TAG, "stop scan successfully");
break;
case ESP_GAP_BLE_ADV_STOP_COMPLETE_EVT:
if (param->adv_stop_cmpl.status != ESP_BT_STATUS_SUCCESS){
ESP_LOGE(GATTC_TAG, "adv stop failed, error status = %x", param->adv_stop_cmpl.status);
break;
}
ESP_LOGI(GATTC_TAG, "stop adv successfully");
break;
case ESP_GAP_BLE_UPDATE_CONN_PARAMS_EVT:
ESP_LOGI(GATTC_TAG, "update connection params status = %d, min_int = %d, max_int = %d,conn_int = %d,latency = %d, timeout = %d",
param->update_conn_params.status,
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;
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)
{
if (event == ESP_GATTC_REG_EVT) {
if (param->reg.status == ESP_GATT_OK) {
gl_profile_tab[PROFILE_APP_ID].gattc_if = gattc_if;
} else {
ESP_LOGI(GATTC_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 (gattc_if == ESP_GATT_IF_NONE || 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)
{
ESP_LOGI(GATTC_TAG, "BLE ACL Latency Test - Central");
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 );
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(GATTC_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(GATTC_TAG, "%s enable controller failed: %s", __func__, esp_err_to_name(ret));
return;
}
ret = esp_bluedroid_init();
if (ret) {
ESP_LOGE(GATTC_TAG, "%s init bluetooth failed: %s", __func__, esp_err_to_name(ret));
return;
}
ret = esp_bluedroid_enable();
if (ret) {
ESP_LOGE(GATTC_TAG, "%s enable bluetooth failed: %s", __func__, esp_err_to_name(ret));
return;
}
ret = esp_ble_gattc_register_callback(esp_gattc_cb);
if(ret){
ESP_LOGE(GATTC_TAG, "%s gattc register failed, error code = %x", __func__, ret);
return;
}
ret = esp_ble_gap_register_callback(esp_gap_cb);
if(ret){
ESP_LOGE(GATTC_TAG, "%s gap register failed, error code = %x", __func__, ret);
return;
}
ret = esp_ble_gattc_app_register(PROFILE_APP_ID);
if (ret){
ESP_LOGE(GATTC_TAG, "%s gattc app register failed, error code = %x", __func__, ret);
}
esp_err_t local_mtu_ret = esp_ble_gatt_set_local_mtu(517);
if (local_mtu_ret){
ESP_LOGE(GATTC_TAG, "set local MTU failed, error code = %x", local_mtu_ret);
}
ESP_LOGI(GATTC_TAG, "Central initialized, scanning for peripheral...");
}
@@ -0,0 +1,261 @@
/*
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "esp_log.h"
#include "esp_timer.h"
#include "esp_random.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_gattc_api.h"
#include "gattc_latency.h"
static const char *TAG = "LATENCY_TEST";
/* Global variables */
static latency_record_t records[TEST_PACKET_COUNT];
static uint16_t test_conn_id = 0;
static uint16_t char_handle = 0;
static test_packet_t send_packets[TEST_PACKET_COUNT];
static bool test_running = false;
static bool test_initialized = false;
static esp_gatt_if_t test_gattc_if = 0;
/**
* Fill random data
*/
static void
fill_random_data(uint8_t *data, uint16_t len)
{
for (int i = 0; i < len; i++) {
data[i] = esp_random() & 0xFF;
}
}
/**
* Initialize latency test
*/
void
latency_test_init(uint16_t conn_id, uint16_t handle)
{
test_conn_id = conn_id;
char_handle = handle;
test_running = false;
test_initialized = true;
memset(records, 0, sizeof(records));
memset(send_packets, 0, sizeof(send_packets));
ESP_LOGI(TAG, "Latency test initialized, conn_id=%d, handle=%d", conn_id, handle);
}
/**
* Set GATT interface
*/
void
latency_test_set_gattc_if(esp_gatt_if_t gattc_if)
{
test_gattc_if = gattc_if;
}
/**
* Send single test packet
*/
static int
send_test_packet(uint16_t seq)
{
if (char_handle == 0 || test_gattc_if == 0) {
ESP_LOGE(TAG, "Test not initialized");
return -1;
}
/* Prepare packet */
test_packet_t *pkt = &send_packets[seq];
pkt->seq_num = seq;
fill_random_data(pkt->payload, sizeof(pkt->payload));
/* Record send time */
records[seq].seq = seq;
records[seq].send_time_us = esp_timer_get_time();
records[seq].received = false;
/* Send write command (write without response) */
esp_err_t ret = esp_ble_gattc_write_char(test_gattc_if,
test_conn_id,
char_handle,
sizeof(test_packet_t),
(uint8_t *)pkt,
ESP_GATT_WRITE_TYPE_NO_RSP,
ESP_GATT_AUTH_REQ_NONE);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Write failed: %s", esp_err_to_name(ret));
return -1;
}
return 0;
}
/**
* Test task
*/
static void
latency_test_task(void *arg)
{
ESP_LOGI(TAG, "====================================");
ESP_LOGI(TAG, "Starting latency test...");
ESP_LOGI(TAG, "Packets: %d, Interval: %d ms", TEST_PACKET_COUNT, TEST_PACKET_INTERVAL_MS);
ESP_LOGI(TAG, "====================================");
test_running = true;
/* Send all test packets */
for (int i = 0; i < TEST_PACKET_COUNT; i++) {
int rc = send_test_packet(i);
if (rc != 0) {
ESP_LOGW(TAG, "Send failed for seq=%d", i);
}
/* Wait interval */
vTaskDelay(pdMS_TO_TICKS(TEST_PACKET_INTERVAL_MS));
}
ESP_LOGI(TAG, "All packets sent, waiting for responses...");
/* Wait for all responses */
vTaskDelay(pdMS_TO_TICKS(2000));
/* Print results */
latency_test_print_results();
test_running = false;
vTaskDelete(NULL);
}
/**
* Start latency test
*/
void
latency_test_start(void)
{
if (test_running) {
ESP_LOGW(TAG, "Test already running");
return;
}
if (!test_initialized || char_handle == 0) {
ESP_LOGE(TAG, "Test not initialized");
return;
}
xTaskCreate(latency_test_task, "latency_test", 4096, NULL, 5, NULL);
}
/**
* Handle notification
*/
void
latency_test_handle_notify(uint8_t *data, uint16_t len)
{
if (!test_running) {
return;
}
/* Record receive time */
int64_t recv_time = esp_timer_get_time();
/* Parse packet */
if (len < sizeof(test_packet_t)) {
ESP_LOGW(TAG, "Invalid packet size: %d", len);
return;
}
test_packet_t *recv_pkt = (test_packet_t *)data;
uint16_t seq = recv_pkt->seq_num;
/* Validate sequence number */
if (seq >= TEST_PACKET_COUNT) {
ESP_LOGW(TAG, "Invalid seq number: %d", seq);
return;
}
/* Verify data consistency */
if (memcmp(recv_pkt, &send_packets[seq], sizeof(test_packet_t)) != 0) {
ESP_LOGW(TAG, "Data mismatch for seq=%d", seq);
return;
}
/* Record receive time */
records[seq].recv_time_us = recv_time;
records[seq].received = true;
/* Calculate latency */
int64_t rtt_us = recv_time - records[seq].send_time_us;
int64_t latency_us = rtt_us / 2;
/* Print real-time result */
printf("SEQ=%3d, RTT=%6lld us, Latency=%6lld us (%.2f ms)\n",
seq, rtt_us, latency_us, (float)latency_us / 1000.0);
}
/**
* Print statistics
*/
void
latency_test_print_results(void)
{
int64_t total_latency = 0;
int64_t min_latency = INT64_MAX;
int64_t max_latency = 0;
int valid_count = 0;
int lost_count = 0;
/* Collect statistics */
for (int i = 0; i < TEST_PACKET_COUNT; i++) {
if (records[i].received) {
int64_t rtt = records[i].recv_time_us - records[i].send_time_us;
int64_t latency = rtt / 2;
total_latency += latency;
valid_count++;
if (latency < min_latency) {
min_latency = latency;
}
if (latency > max_latency) {
max_latency = latency;
}
} else {
lost_count++;
}
}
/* Print results */
ESP_LOGI(TAG, "");
ESP_LOGI(TAG, "========================================");
ESP_LOGI(TAG, " Test Results Summary");
ESP_LOGI(TAG, "========================================");
ESP_LOGI(TAG, "Total packets: %d", TEST_PACKET_COUNT);
ESP_LOGI(TAG, "Received: %d", valid_count);
ESP_LOGI(TAG, "Lost: %d (%.1f%%)", lost_count,
(float)lost_count * 100.0 / TEST_PACKET_COUNT);
if (valid_count > 0) {
int64_t avg_latency = total_latency / valid_count;
ESP_LOGI(TAG, "----------------------------------------");
ESP_LOGI(TAG, "Average latency: %lld us (%.2f ms)", avg_latency,
(float)avg_latency / 1000.0);
ESP_LOGI(TAG, "Min latency: %lld us (%.2f ms)", min_latency,
(float)min_latency / 1000.0);
ESP_LOGI(TAG, "Max latency: %lld us (%.2f ms)", max_latency,
(float)max_latency / 1000.0);
}
ESP_LOGI(TAG, "========================================");
ESP_LOGI(TAG, "");
}
@@ -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_BT_ENABLED=y
# CONFIG_BT_BLE_50_FEATURES_SUPPORTED is not set
CONFIG_BT_BLE_42_FEATURES_SUPPORTED=y
# CONFIG_BT_LE_50_FEATURE_SUPPORT is not set
@@ -0,0 +1,13 @@
# 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_BT_BLE_50_FEATURES_SUPPORTED is not set
CONFIG_BT_BLE_42_FEATURES_SUPPORTED=y
# CONFIG_BT_LE_50_FEATURE_SUPPORT is not set
CONFIG_BT_LE_HCI_EVT_BUF_SIZE=257
# XTAL Freq Config
CONFIG_XTAL_FREQ_26=y
CONFIG_XTAL_FREQ=26
@@ -0,0 +1,6 @@
# 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)
project(ble_acl_latency_periph)
@@ -0,0 +1,124 @@
| Supported Targets | ESP32 | ESP32-C2 | ESP32-C3 | ESP32-C6 | ESP32-H2 | ESP32-S3 |
| ----------------- | ----- | -------- | -------- | -------- | -------- | -------- |
# ESP-IDF BLE ACL Latency Test - Peripheral
This example demonstrates how to measure BLE ACL (Asynchronous Connection-Less) link latency. The peripheral acts as a GATT server that echoes back any data written to it, allowing the central device to measure round-trip time (RTT) and calculate one-way latency.
This demo should be used together with the `cent` (central) example.
## How It Works
1. The peripheral advertises with the device name `ESP_ACL_LATENCY`
2. When a central device connects, the peripheral accepts the connection
3. The peripheral provides a GATT service with a characteristic that supports both write and notify
4. When data is written to the characteristic, the peripheral immediately echoes it back via notification
5. The central measures the time between sending and receiving to calculate RTT and latency
## Test Flow Diagram
```
┌──────────────┐ ┌──────────────┐
│ Central │ │ Peripheral │
│ (GATT Client)│ │ (GATT Server)│
└──────┬───────┘ └──────┬───────┘
│ │
│ │ Advertising
│ │ "ESP_ACL_LATENCY"
│ Scan & Connect │
│ ─────────────────────────────────────────> │
│ │
│ Connection Established │
│ <─────────────────────────────────────────> │
│ │
│ MTU Exchange, Service Discovery │
│ <─────────────────────────────────────────> │
│ │
│ ──────────── Latency Test ───────────── │
│ │
│ Write [SEQ=N, 242 bytes] │
│ ─────────────────────────────────────────> │
│ ┌───────┴───────┐
│ │ Echo the same │
│ │ data back via │
│ │ Notification │
│ └───────┬───────┘
│ Notification [SEQ=N, 242 bytes] │
│ <───────────────────────────────────────── │
│ │
│ ... Repeat for 100 packets ... │
│ │
┌──────┴───────┐ ┌──────┴───────┐
│ Central │ │ Peripheral │
└──────────────┘ └──────────────┘
┌─────────────────────────────────┐
│ Latency Calculation │
├─────────────────────────────────┤
│ RTT = recv_time - send_time │
│ One-way Latency = RTT / 2 │
│ Typical Result: 6-10 ms │
└─────────────────────────────────┘
```
## How to Use Example
### Hardware Required
* Two development boards with supported SoC (e.g., ESP32-C3-DevKitC, ESP32-DevKitC, etc.)
* Two USB cables for power supply and programming
See [Development Boards](https://www.espressif.com/en/products/devkits) for more information.
### Configure the Project
Before project configuration and build, be sure to set the correct chip target using:
```bash
idf.py set-target <chip_name>
```
### Build and Flash
Build the project and flash it to the board:
```bash
idf.py -p PORT flash
```
(Replace PORT with the serial port name, e.g., `/dev/ttyUSB0` on Linux or `COM3` on Windows)
### Monitor
Run `idf.py -p PORT monitor` to view the serial output.
(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 (362) BLE_ACL_LATENCY_PERIPH: BLE ACL Latency Test - Peripheral
I (372) phy_init: phy_version 310,dde1ba9,Jun 4 2024,16:38:11
I (432) BLE_ACL_LATENCY_PERIPH: GATT server register, status=0, app_id=0
I (442) BLE_ACL_LATENCY_PERIPH: Create attribute table successfully, the number handle=4
I (442) BLE_ACL_LATENCY_PERIPH: SERVICE_START_EVT, status=0, service_handle=40
I (452) BLE_ACL_LATENCY_PERIPH: Advertising started
I (462) BLE_ACL_LATENCY_PERIPH: Peripheral ready, waiting for connection...
I (5532) BLE_ACL_LATENCY_PERIPH: ESP_GATTS_CONNECT_EVT, conn_id=0, remote 7c:df:a1:66:a6:00
I (5892) BLE_ACL_LATENCY_PERIPH: MTU exchange, MTU=517
I (5912) BLE_ACL_LATENCY_PERIPH: Connection params updated: status=0, conn_int=6, latency=0, timeout=500
```
## GATT Service Structure
| Attribute | UUID | Properties | Description |
|-----------|--------|-------------------|---------------------------------|
| Service | 0x1234 | - | Latency test service |
| Char | 0x5678 | Write, Notify | Write data, receive echo back |
| CCCD | 0x2902 | Read, Write | Client Characteristic Config |
## Troubleshooting
For any technical queries, please open an [issue](https://github.com/espressif/esp-idf/issues) on GitHub. We will get back to you soon.
@@ -0,0 +1,2 @@
idf_component_register(SRCS "gatts_latency_demo.c"
INCLUDE_DIRS ".")
@@ -0,0 +1,37 @@
/*
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
#ifndef H_GATTS_LATENCY_
#define H_GATTS_LATENCY_
#include "esp_gatts_api.h"
#ifdef __cplusplus
extern "C" {
#endif
/* Service UUID */
#define LATENCY_SERVICE_UUID 0x1234
#define LATENCY_CHAR_UUID 0x5678
/* Profile ID */
#define PROFILE_APP_ID 0
/* Handle table */
enum {
LATENCY_IDX_SVC,
LATENCY_IDX_CHAR,
LATENCY_IDX_CHAR_VAL,
LATENCY_IDX_CHAR_CFG,
LATENCY_IDX_NB,
};
#ifdef __cplusplus
}
#endif
#endif /* H_GATTS_LATENCY_ */
@@ -0,0 +1,360 @@
/*
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.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_main.h"
#include "esp_gatt_common_api.h"
#include "gatts_latency.h"
#define GATTS_TAG "BLE_ACL_LATENCY_PERIPH"
#define DEVICE_NAME "ESP_ACL_LATENCY"
#define GATTS_NUM_HANDLE 4
static uint8_t adv_config_done = 0;
#define adv_config_flag (1 << 0)
#define scan_rsp_config_flag (1 << 1)
/* Advertisement data */
static esp_ble_adv_data_t adv_data = {
.set_scan_rsp = false,
.include_name = true,
.include_txpower = false,
.min_interval = ESP_BLE_GAP_CONN_ITVL_MS(7.5), // 7.5ms (minimum)
.max_interval = ESP_BLE_GAP_CONN_ITVL_MS(20), // 20ms
.appearance = 0x00,
.manufacturer_len = 0,
.p_manufacturer_data = NULL,
.service_data_len = 0,
.p_service_data = NULL,
.service_uuid_len = 0,
.p_service_uuid = NULL,
.flag = (ESP_BLE_ADV_FLAG_GEN_DISC | ESP_BLE_ADV_FLAG_BREDR_NOT_SPT),
};
/* Scan response data */
static esp_ble_adv_data_t scan_rsp_data = {
.set_scan_rsp = true,
.include_name = true,
.include_txpower = true,
.appearance = 0x00,
.manufacturer_len = 0,
.p_manufacturer_data = NULL,
.service_data_len = 0,
.p_service_data = NULL,
.service_uuid_len = 0,
.p_service_uuid = NULL,
.flag = (ESP_BLE_ADV_FLAG_GEN_DISC | ESP_BLE_ADV_FLAG_BREDR_NOT_SPT),
};
static esp_ble_adv_params_t adv_params = {
.adv_int_min = ESP_BLE_GAP_ADV_ITVL_MS(20), // 20ms
.adv_int_max = ESP_BLE_GAP_ADV_ITVL_MS(40), // 40ms
.adv_type = ADV_TYPE_IND,
.own_addr_type = BLE_ADDR_TYPE_PUBLIC,
.channel_map = ADV_CHNL_ALL,
.adv_filter_policy = ADV_FILTER_ALLOW_SCAN_ANY_CON_ANY,
};
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 */
#define PROFILE_NUM 1
#define PROFILE_APP_IDX 0
static struct gatts_profile_inst gl_profile_tab[PROFILE_NUM] = {
[PROFILE_APP_IDX] = {
.gatts_cb = gatts_profile_event_handler,
.gatts_if = ESP_GATT_IF_NONE,
},
};
/* Service */
static const uint16_t GATTS_SERVICE_UUID = LATENCY_SERVICE_UUID;
static const uint16_t GATTS_CHAR_UUID = LATENCY_CHAR_UUID;
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_write_notify = ESP_GATT_CHAR_PROP_BIT_WRITE | ESP_GATT_CHAR_PROP_BIT_NOTIFY;
static uint8_t char_ccc[2] = {0x00, 0x00};
/* Full Database Description - Used to add attributes into the database */
static const esp_gatts_attr_db_t gatt_db[LATENCY_IDX_NB] =
{
/* Service Declaration */
[LATENCY_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), (uint8_t *)&GATTS_SERVICE_UUID}},
/* Characteristic Declaration */
[LATENCY_IDX_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_write_notify}},
/* Characteristic Value */
[LATENCY_IDX_CHAR_VAL] =
{{ESP_GATT_RSP_BY_APP}, {ESP_UUID_LEN_16, (uint8_t *)&GATTS_CHAR_UUID, ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE,
512, 0, NULL}},
/* Client Characteristic Configuration Descriptor */
[LATENCY_IDX_CHAR_CFG] =
{{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_ccc), (uint8_t *)char_ccc}},
};
static uint16_t handle_table[LATENCY_IDX_NB];
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_ADV_DATA_SET_COMPLETE_EVT:
adv_config_done &= (~adv_config_flag);
if (adv_config_done == 0){
esp_ble_gap_start_advertising(&adv_params);
}
break;
case ESP_GAP_BLE_SCAN_RSP_DATA_SET_COMPLETE_EVT:
adv_config_done &= (~scan_rsp_config_flag);
if (adv_config_done == 0){
esp_ble_gap_start_advertising(&adv_params);
}
break;
case ESP_GAP_BLE_ADV_START_COMPLETE_EVT:
if (param->adv_start_cmpl.status != ESP_BT_STATUS_SUCCESS) {
ESP_LOGE(GATTS_TAG, "Advertising start failed");
} else {
ESP_LOGI(GATTS_TAG, "Advertising started");
}
break;
case ESP_GAP_BLE_ADV_STOP_COMPLETE_EVT:
if (param->adv_stop_cmpl.status != ESP_BT_STATUS_SUCCESS) {
ESP_LOGE(GATTS_TAG, "Advertising stop failed");
} else {
ESP_LOGI(GATTS_TAG, "Advertising stopped");
}
break;
case ESP_GAP_BLE_UPDATE_CONN_PARAMS_EVT:
ESP_LOGI(GATTS_TAG, "Connection params updated: 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(GATTS_TAG, "GATT server register, status=%d, app_id=%d", param->reg.status, param->reg.app_id);
gl_profile_tab[PROFILE_APP_IDX].service_id.is_primary = true;
gl_profile_tab[PROFILE_APP_IDX].service_id.id.inst_id = 0x00;
gl_profile_tab[PROFILE_APP_IDX].service_id.id.uuid.len = ESP_UUID_LEN_16;
gl_profile_tab[PROFILE_APP_IDX].service_id.id.uuid.uuid.uuid16 = GATTS_SERVICE_UUID;
esp_err_t set_dev_name_ret = esp_ble_gap_set_device_name(DEVICE_NAME);
if (set_dev_name_ret){
ESP_LOGE(GATTS_TAG, "Set device name failed, error code = %x", set_dev_name_ret);
}
esp_err_t ret = esp_ble_gap_config_adv_data(&adv_data);
if (ret){
ESP_LOGE(GATTS_TAG, "Config adv data failed, error code = %x", ret);
}
adv_config_done |= adv_config_flag;
ret = esp_ble_gap_config_adv_data(&scan_rsp_data);
if (ret){
ESP_LOGE(GATTS_TAG, "Config scan response data failed, error code = %x", ret);
}
adv_config_done |= scan_rsp_config_flag;
esp_err_t create_attr_ret = esp_ble_gatts_create_attr_tab(gatt_db, gatts_if, LATENCY_IDX_NB, 0);
if (create_attr_ret){
ESP_LOGE(GATTS_TAG, "Create attr table failed, error code = %x", create_attr_ret);
}
}
break;
case ESP_GATTS_READ_EVT:
ESP_LOGD(GATTS_TAG, "ESP_GATTS_READ_EVT");
break;
case ESP_GATTS_WRITE_EVT:
if (!param->write.is_prep){
ESP_LOGD(GATTS_TAG, "GATT_WRITE_EVT, handle=%d, value len=%d", param->write.handle, param->write.len);
if (handle_table[LATENCY_IDX_CHAR_VAL] == param->write.handle) {
/* Echo the data back via notification */
esp_ble_gatts_send_indicate(gatts_if, param->write.conn_id, handle_table[LATENCY_IDX_CHAR_VAL],
param->write.len, param->write.value, false);
}
}
break;
case ESP_GATTS_EXEC_WRITE_EVT:
case ESP_GATTS_MTU_EVT:
ESP_LOGI(GATTS_TAG, "MTU exchange, MTU=%d", param->mtu.mtu);
break;
case ESP_GATTS_CONF_EVT:
break;
case ESP_GATTS_START_EVT:
ESP_LOGI(GATTS_TAG, "SERVICE_START_EVT, status=%d, service_handle=%d", param->start.status, param->start.service_handle);
break;
case ESP_GATTS_CONNECT_EVT:
ESP_LOGI(GATTS_TAG, "ESP_GATTS_CONNECT_EVT, conn_id=%d, remote "ESP_BD_ADDR_STR"",
param->connect.conn_id, ESP_BD_ADDR_HEX(param->connect.remote_bda));
gl_profile_tab[PROFILE_APP_IDX].conn_id = param->connect.conn_id;
break;
case ESP_GATTS_DISCONNECT_EVT:
ESP_LOGI(GATTS_TAG, "ESP_GATTS_DISCONNECT_EVT, reason=0x%x", param->disconnect.reason);
esp_ble_gap_start_advertising(&adv_params);
break;
case ESP_GATTS_CREAT_ATTR_TAB_EVT:{
if (param->add_attr_tab.status != ESP_GATT_OK){
ESP_LOGE(GATTS_TAG, "Create attribute table failed, error code=0x%x", param->add_attr_tab.status);
}
else if (param->add_attr_tab.num_handle != LATENCY_IDX_NB){
ESP_LOGE(GATTS_TAG, "Create attribute table abnormally, num_handle (%d) \
doesn't equal to LATENCY_IDX_NB(%d)", param->add_attr_tab.num_handle, LATENCY_IDX_NB);
}
else {
ESP_LOGI(GATTS_TAG, "Create attribute table successfully, the number handle=%d",param->add_attr_tab.num_handle);
memcpy(handle_table, param->add_attr_tab.handles, sizeof(handle_table));
esp_ble_gatts_start_service(handle_table[LATENCY_IDX_SVC]);
}
break;
}
case ESP_GATTS_STOP_EVT:
case ESP_GATTS_OPEN_EVT:
case ESP_GATTS_CANCEL_OPEN_EVT:
case ESP_GATTS_CLOSE_EVT:
case ESP_GATTS_LISTEN_EVT:
case ESP_GATTS_CONGEST_EVT:
case ESP_GATTS_UNREG_EVT:
case ESP_GATTS_DELETE_EVT:
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 == ESP_GATTS_REG_EVT) {
if (param->reg.status == ESP_GATT_OK) {
gl_profile_tab[PROFILE_APP_IDX].gatts_if = gatts_if;
} else {
ESP_LOGE(GATTS_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;
ESP_LOGI(GATTS_TAG, "BLE ACL Latency Test - Peripheral");
/* 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(GATTS_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(GATTS_TAG, "%s enable controller failed: %s", __func__, esp_err_to_name(ret));
return;
}
ret = esp_bluedroid_init();
if (ret) {
ESP_LOGE(GATTS_TAG, "%s init bluetooth failed: %s", __func__, esp_err_to_name(ret));
return;
}
ret = esp_bluedroid_enable();
if (ret) {
ESP_LOGE(GATTS_TAG, "%s enable bluetooth failed: %s", __func__, esp_err_to_name(ret));
return;
}
ret = esp_ble_gatts_register_callback(gatts_event_handler);
if (ret){
ESP_LOGE(GATTS_TAG, "gatts register error, error code = %x", ret);
return;
}
ret = esp_ble_gap_register_callback(gap_event_handler);
if (ret){
ESP_LOGE(GATTS_TAG, "gap register error, error code = %x", ret);
return;
}
ret = esp_ble_gatts_app_register(PROFILE_APP_ID);
if (ret){
ESP_LOGE(GATTS_TAG, "gatts app register error, error code = %x", ret);
return;
}
esp_err_t local_mtu_ret = esp_ble_gatt_set_local_mtu(517);
if (local_mtu_ret){
ESP_LOGE(GATTS_TAG, "set local MTU failed, error code = %x", local_mtu_ret);
}
ESP_LOGI(GATTS_TAG, "Peripheral ready, waiting for connection...");
}
@@ -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_BT_ENABLED=y
# CONFIG_BT_BLE_50_FEATURES_SUPPORTED is not set
CONFIG_BT_BLE_42_FEATURES_SUPPORTED=y
# CONFIG_BT_LE_50_FEATURE_SUPPORT is not set
@@ -0,0 +1,13 @@
# 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_BT_BLE_50_FEATURES_SUPPORTED is not set
CONFIG_BT_BLE_42_FEATURES_SUPPORTED=y
# CONFIG_BT_LE_50_FEATURE_SUPPORT is not set
CONFIG_BT_LE_HCI_EVT_BUF_SIZE=257
# XTAL Freq Config
CONFIG_XTAL_FREQ_26=y
CONFIG_XTAL_FREQ=26