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:
@@ -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 *)¬ify_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
|
||||
Reference in New Issue
Block a user