mirror of
https://github.com/espressif/esp-idf.git
synced 2026-10-03 03:31:41 +03:00
fix(nimble): Added l2cap_coc throughput examples
This commit is contained in:
@@ -0,0 +1,8 @@
|
||||
# The following lines of boilerplate have to be in your project's
|
||||
# CMakeLists in this exact order for cmake to work correctly
|
||||
cmake_minimum_required(VERSION 3.22)
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
# "Trim" the build. Include the minimal set of components, main, and anything it depends on.
|
||||
idf_build_set_property(MINIMAL_BUILD ON)
|
||||
project(blecent_throughput)
|
||||
@@ -0,0 +1,162 @@
|
||||
| Supported Targets | ESP32 | ESP32-C2 | ESP32-C3 | ESP32-C5 | ESP32-C6 | ESP32-C61 | ESP32-H2 | ESP32-S3 |
|
||||
| ----------------- | ----- | -------- | -------- | -------- | -------- | --------- | -------- | -------- |
|
||||
|
||||
# Throughput blecent Example
|
||||
|
||||
(See the README.md file in the upper level 'examples' directory for more information about examples.)
|
||||
|
||||
`blecent_throughput` demonstrates client side implementation required for NimBLE throughput example. It connects to `bleprph_throughput` based on name string `nimble_prph`. It has interactive CLI support to start READ/WRITE/NOTIFY GATT operation for user specified time.
|
||||
|
||||
It performs read operations on the peripheral's `THRPT_LONG_CHR_READ_WRITE` characteristic (510 bytes), write operations on `THRPT_CHR_READ_WRITE` (509-byte payload), and subscribes to `THRPT_CHR_NOTIFY` for notifications. If the user does not specify a throughput test method within 30 seconds (`BLE_RX_TIMEOUT`), the program defaults to READ operations for 60 seconds.
|
||||
|
||||
`blecent_throughput` uses the `NimBLE` as BLE host.
|
||||
|
||||
### Procedure to Use This Demo Example
|
||||
|
||||
* `idf.py menuconfig` and configure the parameters as needed (connection related parameters in Example Configuration).
|
||||
* The default connection interval is set to 7.5ms (6 units) for maximum throughput. This can be changed via menuconfig or at runtime.
|
||||
* To test throughput on 1M PHY, make sure to disable the Example Configuration -> Enable Extended Adv flag in menuconfig on both sides.
|
||||
* To test on 2M PHY or Coded PHY (S2/S8), you must enable the Example Configuration -> Enable Extended Adv flag on both sides.
|
||||
* `bleprph_throughput` example needs to be run along with this client side example.
|
||||
* After the connection link is established between the two devices, the user is given a window of `YES_NO_PARAM` (5 seconds) to customize connection parameters. If the user has configured parameters from menuconfig, this step can be skipped by either waiting it out or entering `Insert No`.
|
||||
* Enter `Insert Yes` to customize connection parameters. Enter `MTU` and other connection parameters as directed on console instructions, e.g. `MTU 512` for MTU and `conn 6 6 0 500 12 24` for connection parameters in sequence of `min conn_itvl`, `max conn_itvl`, `latency`, `supervision timeout`, `min conn_evt_len` and `max_conn_evt_len`.
|
||||
* The user will be presented with throughput test related console prints. The prints may appear like below
|
||||
|
||||
```
|
||||
====================================================================================
|
||||
| Steps to test nimble throughput |
|
||||
| |
|
||||
| 1. Enter throughput [--Type] [--Test time] [--Phy] |
|
||||
| Type: read/write/notify. |
|
||||
| Test time: Enter value in seconds. |
|
||||
| Phy mode: Enter value in 0 for 1M, 1 for 2M ,2 for Coded S2, |
|
||||
| 3 for Coded S8. |
|
||||
| |
|
||||
| e.g. throughput read 600 3 |
|
||||
| |
|
||||
| ** Enter 'throughput read 60 0' for reading char for 60 seconds on 1M phy |
|
||||
| OR 'throughput write 60 1' for writing to char for 60 seconds on 2M phy |
|
||||
| OR 'throughput notify 60 2' for notifications (for 60 seconds on s=2 coded phy)**|
|
||||
| |
|
||||
=====================================================================================
|
||||
|
||||
|
||||
```
|
||||
* If the user fails to enter any values within 30 seconds, the app falls to the default behavior of READ for 60 seconds on 1M PHY.
|
||||
* Read and write throughput numbers are displayed on the `blecent_throughput` console. For notification throughput, refer to the `bleprph_throughput` console, as the peripheral is the one sending notifications. Below is sample output:
|
||||
|
||||
```
|
||||
Type 'help' to get the list of commands.
|
||||
Use UP/DOWN arrows to navigate through command history.
|
||||
Press TAB when typing command name to auto-complete.
|
||||
I (1123) CLI: BLE CLI registered
|
||||
I (1123) blecent_throughput: BLE Host Task Started
|
||||
|
||||
===============================================================================================
|
||||
| Steps to test nimble throughput |
|
||||
| |
|
||||
| 1. Print 'help' to gain overview of commands |
|
||||
| |
|
||||
I (1163) blecent_throughput: Do you want to configure connection params ?
|
||||
I (1183) blecent_throughput: If yes then enter in this format: `Insert Yes`
|
||||
|
||||
===============================================================================================
|
||||
Throughput demo >> GAP procedure initiated: discovery; own_addr_type=0 filter_policy=0 passive=1 limited=0 filter_duplicates=1 duration=forever
|
||||
I (6213) blecent_throughput: Event DISC
|
||||
I (6213) blecent_throughput: connect; fields.num_uuids128 =0
|
||||
I (6223) blecent_throughput: central connect to `nimble_prph` success
|
||||
GAP procedure initiated: connect; peer_addr_type=1 peer_addr=14:58:11:c1:d0:2d scan_itvl=16 scan_window=16 itvl_min=24 itvl_max=40 latency=0 supervision_timeout=256 min_ce_len=0 max_ce_len=0 own_addr_type=0
|
||||
E (36243) blecent_throughput: Error: Connection failed; status = 13
|
||||
GAP procedure initiated: discovery; own_addr_type=0 filter_policy=0 passive=1 limited=0 filter_duplicates=1 duration=forever
|
||||
I (36293) blecent_throughput: Event DISC
|
||||
I (36293) blecent_throughput: connect; fields.num_uuids128 =0
|
||||
I (36293) blecent_throughput: Name = nimble_prph
|
||||
ל�j6�^n����
|
||||
I (36303) blecent_throughput: central connect to `nimble_prph` success
|
||||
GAP procedure initiated: connect; peer_addr_type=0 peer_addr=84:0d:8e:e6:83:de scan_itvl=16 scan_window=16 itvl_min=24 itvl_max=40 latency=0 supervision_timeout=256 min_ce_len=0 max_ce_len=0 own_addr_type=0
|
||||
I (36393) blecent_throughput: Connection established
|
||||
GATT procedure initiated: exchange mtu
|
||||
GAP procedure initiated: connection parameter update; conn_handle=0 itvl_min=6 itvl_max=6 latency=0 supervision_timeout=500 min_ce_len=8 max_ce_len=768
|
||||
GATT procedure initiated: discover all services
|
||||
I (36783) blecent_throughput: mtu update event; conn_handle = 0 cid = 4 mtu = 512
|
||||
GATT procedure initiated: discover all characteristics; start_handle=1 end_handle=5
|
||||
GATT procedure initiated: discover all characteristics; start_handle=6 end_handle=9
|
||||
GATT procedure initiated: discover all characteristics; start_handle=10 end_handle=65535
|
||||
GATT procedure initiated: discover all descriptors; chr_val_handle=8 end_handle=9
|
||||
GATT procedure initiated: discover all descriptors; chr_val_handle=14 end_handle=15
|
||||
GATT procedure initiated: discover all descriptors; chr_val_handle=17 end_handle=65535
|
||||
I (36933) blecent_throughput: Service discovery complete; status=0 conn_handle=0
|
||||
|
||||
I (36933) blecent_throughput: Format for throughput demo:: throughput read 100
|
||||
====================================================================================
|
||||
| Steps to test nimble throughput |
|
||||
| |
|
||||
| 1. Enter throughput [--Type] [--Test time] [--Phy] |
|
||||
| Type: read/write/notify. |
|
||||
| Test time: Enter value in seconds. |
|
||||
| Phy mode: Enter value in 0 for 1M, 1 for 2M ,2 for Coded S2, |
|
||||
| 3 for Coded S8. |
|
||||
| |
|
||||
| e.g. throughput read 600 3 |
|
||||
| |
|
||||
| ** Enter 'throughput read 60 0' for reading char for 60 seconds on 1M phy |
|
||||
| OR 'throughput write 60 1' for writing to char for 60 seconds on 2M phy |
|
||||
| OR 'throughput notify 60 2' for notifications (for 60 seconds on s=2 coded phy)**|
|
||||
| |
|
||||
=====================================================================================
|
||||
|
||||
|
||||
Throughput demo >> throughput read 10
|
||||
I (55333) Throughput demo handler: throughput read 10
|
||||
GATT procedure initiated: read; att_handle=17
|
||||
GATT procedure initiated: read; att_handle=17
|
||||
GATT procedure initiated: read; att_handle=17
|
||||
GATT procedure initiated: read; att_handle=17
|
||||
Throughput demo >> GATT procedure initiated: read; att_handle=17
|
||||
GATT procedure initiated: read; att_handle=17
|
||||
GATT procedure initiated: read; att_handle=17
|
||||
GATT procedure initiated: read; att_handle=17
|
||||
GATT procedure initiated: read; att_handle=17
|
||||
GATT procedure initiated: read; att_handle=17
|
||||
GATT procedure initiated: read; att_handle=17
|
||||
GATT procedure initiated: read; att_handle=17
|
||||
GATT procedure initiated: read; att_handle=17
|
||||
GATT procedure initiated: read; att_handle=17
|
||||
GATT procedure initiated: read; att_handle=17
|
||||
```
|
||||
|
||||
### Build and Flash
|
||||
|
||||
Build the project and flash it to the board, then run monitor tool to view serial output:
|
||||
|
||||
```
|
||||
idf.py -p PORT flash monitor
|
||||
```
|
||||
|
||||
(To exit the serial monitor, type ``Ctrl-]``.)
|
||||
|
||||
See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects.
|
||||
|
||||
## Example Scope
|
||||
|
||||
This demo example demonstrates stable GATT read/write and notify operations at high throughput. The READ and WRITE GATT operations require an ACK from the peer, so this central app ensures that the next GATT operation is performed after completion of the previous one. For write operations, `ble_gattc_write_no_rsp_flat` is used (Write Without Response) which does not require an ATT-level ACK, allowing higher write throughput. If the mbuf pool is exhausted (`BLE_HS_ENOMEM`), the app yields briefly and retries.
|
||||
|
||||
For notifications (`bleprph_throughput` sends), the peripheral uses pipelined notification sending with a configurable pipeline depth, allowing the controller to pack multiple PDUs per connection event. Both sides are configured with `CONFIG_BT_NIMBLE_MSYS_1_BLOCK_COUNT=50` to provide sufficient buffer space for high-throughput operations.
|
||||
|
||||
## Example output
|
||||
|
||||
The `Procedure to use this demo example` section above covers initial console outputs. In this section we provide final console output depicting throughput numbers
|
||||
|
||||
```
|
||||
GATT procedure initiated: read; att_handle=17
|
||||
GATT procedure initiated: read; att_handle=17
|
||||
GATT procedure initiated: read; att_handle=17
|
||||
GATT procedure initiated: read; att_handle=17
|
||||
|
||||
****************************************************************
|
||||
I (65363) blecent_throughput: Application Read throughput = 106800 bps, Read op counter = 268
|
||||
|
||||
****************************************************************
|
||||
|
||||
```
|
||||
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
idf_component_register(SRCS "cmd_system.c"
|
||||
INCLUDE_DIRS "."
|
||||
PRIV_REQUIRES esp_driver_uart esp_driver_gpio
|
||||
REQUIRES console spi_flash)
|
||||
+372
@@ -0,0 +1,372 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2015-2025 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <ctype.h>
|
||||
#include "esp_log.h"
|
||||
#include "esp_console.h"
|
||||
#include "esp_chip_info.h"
|
||||
#include "esp_sleep.h"
|
||||
#include "esp_flash.h"
|
||||
#include "driver/rtc_io.h"
|
||||
#include "driver/gpio.h"
|
||||
#include "driver/uart.h"
|
||||
#include "argtable3/argtable3.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "cmd_system.h"
|
||||
#include "sdkconfig.h"
|
||||
|
||||
#ifdef CONFIG_FREERTOS_USE_STATS_FORMATTING_FUNCTIONS
|
||||
#define WITH_TASKS_INFO 1
|
||||
#endif
|
||||
|
||||
static const char *TAG = "cmd_system";
|
||||
|
||||
static void register_free(void);
|
||||
static void register_heap(void);
|
||||
static void register_version(void);
|
||||
static void register_restart(void);
|
||||
#if SOC_DEEP_SLEEP_SUPPORTED
|
||||
static void register_deep_sleep(void);
|
||||
#endif
|
||||
#if SOC_LIGHT_SLEEP_SUPPORTED
|
||||
static void register_light_sleep(void);
|
||||
#endif
|
||||
#if WITH_TASKS_INFO
|
||||
static void register_tasks(void);
|
||||
#endif
|
||||
|
||||
void register_system(void)
|
||||
{
|
||||
register_free();
|
||||
register_heap();
|
||||
register_version();
|
||||
register_restart();
|
||||
#if SOC_DEEP_SLEEP_SUPPORTED
|
||||
register_deep_sleep();
|
||||
#endif
|
||||
#if SOC_LIGHT_SLEEP_SUPPORTED
|
||||
register_light_sleep();
|
||||
#endif
|
||||
#if WITH_TASKS_INFO
|
||||
register_tasks();
|
||||
#endif
|
||||
}
|
||||
|
||||
/* 'version' command */
|
||||
static int get_version(int argc, char **argv)
|
||||
{
|
||||
esp_chip_info_t info;
|
||||
uint32_t flash_size;
|
||||
const char *model_str;
|
||||
esp_chip_info(&info);
|
||||
if(esp_flash_get_size(NULL, &flash_size) != ESP_OK) {
|
||||
printf("Get flash size failed");
|
||||
return 1;
|
||||
}
|
||||
printf("IDF Version:%s\r\n", esp_get_idf_version());
|
||||
printf("Chip info:\r\n");
|
||||
switch (info.model) {
|
||||
case CHIP_ESP32: model_str = "ESP32"; break;
|
||||
case CHIP_ESP32S3: model_str = "ESP32-S3"; break;
|
||||
case CHIP_ESP32C3: model_str = "ESP32-C3"; break;
|
||||
case CHIP_ESP32C2: model_str = "ESP32-C2"; break;
|
||||
case CHIP_ESP32C6: model_str = "ESP32-C6"; break;
|
||||
case CHIP_ESP32H2: model_str = "ESP32-H2"; break;
|
||||
case CHIP_ESP32C61: model_str = "ESP32-C61"; break;
|
||||
case CHIP_ESP32C5: model_str = "ESP32-C5"; break;
|
||||
default: model_str = "Unknown"; break;
|
||||
}
|
||||
printf("\tmodel:%s\r\n", model_str);
|
||||
printf("\tcores:%d\r\n", info.cores);
|
||||
printf("\tfeature:%s%s%s%s%" PRIu32 "%s\r\n",
|
||||
info.features & CHIP_FEATURE_WIFI_BGN ? "/802.11bgn" : "",
|
||||
info.features & CHIP_FEATURE_BLE ? "/BLE" : "",
|
||||
info.features & CHIP_FEATURE_BT ? "/BT" : "",
|
||||
info.features & CHIP_FEATURE_EMB_FLASH ? "/Embedded-Flash:" : "/External-Flash:",
|
||||
flash_size / (1024 * 1024), " MB");
|
||||
printf("\trevision number:%d\r\n", info.revision);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void register_version(void)
|
||||
{
|
||||
const esp_console_cmd_t cmd = {
|
||||
.command = "version",
|
||||
.help = "Get version of chip and SDK",
|
||||
.hint = NULL,
|
||||
.func = &get_version,
|
||||
};
|
||||
ESP_ERROR_CHECK( esp_console_cmd_register(&cmd) );
|
||||
}
|
||||
|
||||
/** 'restart' command restarts the program */
|
||||
|
||||
static int restart(int argc, char **argv)
|
||||
{
|
||||
ESP_LOGI(TAG, "Restarting");
|
||||
esp_restart();
|
||||
}
|
||||
|
||||
static void register_restart(void)
|
||||
{
|
||||
const esp_console_cmd_t cmd = {
|
||||
.command = "restart",
|
||||
.help = "Software reset of the chip",
|
||||
.hint = NULL,
|
||||
.func = &restart,
|
||||
};
|
||||
ESP_ERROR_CHECK( esp_console_cmd_register(&cmd) );
|
||||
}
|
||||
|
||||
/** 'free' command prints available heap memory */
|
||||
|
||||
static int free_mem(int argc, char **argv)
|
||||
{
|
||||
printf("%" PRIu32 "\n", esp_get_free_heap_size());
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void register_free(void)
|
||||
{
|
||||
const esp_console_cmd_t cmd = {
|
||||
.command = "free",
|
||||
.help = "Get the current size of free heap memory",
|
||||
.hint = NULL,
|
||||
.func = &free_mem,
|
||||
};
|
||||
ESP_ERROR_CHECK( esp_console_cmd_register(&cmd) );
|
||||
}
|
||||
|
||||
/* 'heap' command prints minimum heap size */
|
||||
static int heap_size(int argc, char **argv)
|
||||
{
|
||||
uint32_t heap_size = heap_caps_get_minimum_free_size(MALLOC_CAP_DEFAULT);
|
||||
ESP_LOGI(TAG, "min heap size: %" PRIu32 , heap_size);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void register_heap(void)
|
||||
{
|
||||
const esp_console_cmd_t heap_cmd = {
|
||||
.command = "heap",
|
||||
.help = "Get minimum size of free heap memory that was available during program execution",
|
||||
.hint = NULL,
|
||||
.func = &heap_size,
|
||||
};
|
||||
ESP_ERROR_CHECK( esp_console_cmd_register(&heap_cmd) );
|
||||
|
||||
}
|
||||
|
||||
/** 'tasks' command prints the list of tasks and related information */
|
||||
#if WITH_TASKS_INFO
|
||||
|
||||
static int tasks_info(int argc, char **argv)
|
||||
{
|
||||
const size_t bytes_per_task = 40; /* see vTaskList description */
|
||||
char *task_list_buffer = malloc(uxTaskGetNumberOfTasks() * bytes_per_task);
|
||||
if (task_list_buffer == NULL) {
|
||||
ESP_LOGE(TAG, "failed to allocate buffer for vTaskList output");
|
||||
return 1;
|
||||
}
|
||||
fputs("Task Name\tStatus\tPrio\tHWM\tTask#", stdout);
|
||||
#ifdef CONFIG_FREERTOS_VTASKLIST_INCLUDE_COREID
|
||||
fputs("\tAffinity", stdout);
|
||||
#endif
|
||||
fputs("\n", stdout);
|
||||
vTaskList(task_list_buffer);
|
||||
fputs(task_list_buffer, stdout);
|
||||
free(task_list_buffer);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void register_tasks(void)
|
||||
{
|
||||
const esp_console_cmd_t cmd = {
|
||||
.command = "tasks",
|
||||
.help = "Get information about running tasks",
|
||||
.hint = NULL,
|
||||
.func = &tasks_info,
|
||||
};
|
||||
ESP_ERROR_CHECK( esp_console_cmd_register(&cmd) );
|
||||
}
|
||||
|
||||
#endif // WITH_TASKS_INFO
|
||||
|
||||
#if SOC_DEEP_SLEEP_SUPPORTED
|
||||
/** 'deep_sleep' command puts the chip into deep sleep mode */
|
||||
|
||||
static struct {
|
||||
struct arg_int *wakeup_time;
|
||||
struct arg_int *wakeup_gpio_num;
|
||||
struct arg_int *wakeup_gpio_level;
|
||||
struct arg_end *end;
|
||||
} deep_sleep_args;
|
||||
|
||||
|
||||
static int deep_sleep(int argc, char **argv)
|
||||
{
|
||||
int nerrors = arg_parse(argc, argv, (void **) &deep_sleep_args);
|
||||
if (nerrors != 0) {
|
||||
arg_print_errors(stderr, deep_sleep_args.end, argv[0]);
|
||||
return 1;
|
||||
}
|
||||
if (deep_sleep_args.wakeup_time->count) {
|
||||
uint64_t timeout = 1000ULL * deep_sleep_args.wakeup_time->ival[0];
|
||||
ESP_LOGI(TAG, "Enabling timer wakeup, timeout=%lluus", timeout);
|
||||
ESP_ERROR_CHECK( esp_sleep_enable_timer_wakeup(timeout) );
|
||||
}
|
||||
if (deep_sleep_args.wakeup_gpio_num->count) {
|
||||
int io_num = deep_sleep_args.wakeup_gpio_num->ival[0];
|
||||
if (!rtc_gpio_is_valid_gpio(io_num)) {
|
||||
ESP_LOGE(TAG, "GPIO %d is not an RTC IO", io_num);
|
||||
return 1;
|
||||
}
|
||||
int level = 0;
|
||||
if (deep_sleep_args.wakeup_gpio_level->count) {
|
||||
level = deep_sleep_args.wakeup_gpio_level->ival[0];
|
||||
if (level != 0 && level != 1) {
|
||||
ESP_LOGE(TAG, "Invalid wakeup level: %d", level);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
ESP_LOGI(TAG, "Enabling wakeup on GPIO%d, wakeup on %s level",
|
||||
io_num, level ? "HIGH" : "LOW");
|
||||
|
||||
#if SOC_PM_SUPPORT_EXT1_WAKEUP
|
||||
ESP_ERROR_CHECK( esp_sleep_enable_ext1_wakeup_io(1ULL << io_num, level) );
|
||||
#endif
|
||||
}
|
||||
|
||||
#if CONFIG_IDF_TARGET_ESP32
|
||||
rtc_gpio_isolate(GPIO_NUM_12);
|
||||
#endif
|
||||
esp_deep_sleep_start();
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void register_deep_sleep(void)
|
||||
{
|
||||
deep_sleep_args.wakeup_time =
|
||||
arg_int0("t", "time", "<t>", "Wake up time, ms");
|
||||
deep_sleep_args.wakeup_gpio_num =
|
||||
arg_int0(NULL, "io", "<n>",
|
||||
"If specified, wakeup using GPIO with given number");
|
||||
deep_sleep_args.wakeup_gpio_level =
|
||||
arg_int0(NULL, "io_level", "<0|1>", "GPIO level to trigger wakeup");
|
||||
deep_sleep_args.end = arg_end(3);
|
||||
|
||||
const esp_console_cmd_t cmd = {
|
||||
.command = "deep_sleep",
|
||||
.help = "Enter deep sleep mode. "
|
||||
"Two wakeup modes are supported: timer and GPIO. "
|
||||
"If no wakeup option is specified, will sleep indefinitely.",
|
||||
.hint = NULL,
|
||||
.func = &deep_sleep,
|
||||
.argtable = &deep_sleep_args
|
||||
};
|
||||
ESP_ERROR_CHECK( esp_console_cmd_register(&cmd) );
|
||||
}
|
||||
#endif // SOC_DEEP_SLEEP_SUPPORTED
|
||||
|
||||
#if SOC_LIGHT_SLEEP_SUPPORTED
|
||||
/** 'light_sleep' command puts the chip into light sleep mode */
|
||||
|
||||
static struct {
|
||||
struct arg_int *wakeup_time;
|
||||
struct arg_int *wakeup_gpio_num;
|
||||
struct arg_int *wakeup_gpio_level;
|
||||
struct arg_end *end;
|
||||
} light_sleep_args;
|
||||
|
||||
static int light_sleep(int argc, char **argv)
|
||||
{
|
||||
int nerrors = arg_parse(argc, argv, (void **) &light_sleep_args);
|
||||
if (nerrors != 0) {
|
||||
arg_print_errors(stderr, light_sleep_args.end, argv[0]);
|
||||
return 1;
|
||||
}
|
||||
esp_sleep_disable_wakeup_source(ESP_SLEEP_WAKEUP_ALL);
|
||||
if (light_sleep_args.wakeup_time->count) {
|
||||
uint64_t timeout = 1000ULL * light_sleep_args.wakeup_time->ival[0];
|
||||
ESP_LOGI(TAG, "Enabling timer wakeup, timeout=%lluus", timeout);
|
||||
ESP_ERROR_CHECK( esp_sleep_enable_timer_wakeup(timeout) );
|
||||
}
|
||||
int io_count = light_sleep_args.wakeup_gpio_num->count;
|
||||
if (io_count != light_sleep_args.wakeup_gpio_level->count) {
|
||||
ESP_LOGE(TAG, "Should have same number of 'io' and 'io_level' arguments");
|
||||
return 1;
|
||||
}
|
||||
for (int i = 0; i < io_count; ++i) {
|
||||
int io_num = light_sleep_args.wakeup_gpio_num->ival[i];
|
||||
int level = light_sleep_args.wakeup_gpio_level->ival[i];
|
||||
if (level != 0 && level != 1) {
|
||||
ESP_LOGE(TAG, "Invalid wakeup level: %d", level);
|
||||
return 1;
|
||||
}
|
||||
ESP_LOGI(TAG, "Enabling wakeup on GPIO%d, wakeup on %s level",
|
||||
io_num, level ? "HIGH" : "LOW");
|
||||
|
||||
ESP_ERROR_CHECK( gpio_wakeup_enable(io_num, level ? GPIO_INTR_HIGH_LEVEL : GPIO_INTR_LOW_LEVEL) );
|
||||
}
|
||||
if (io_count > 0) {
|
||||
ESP_ERROR_CHECK( esp_sleep_enable_gpio_wakeup() );
|
||||
}
|
||||
if (CONFIG_ESP_CONSOLE_UART_NUM <= UART_NUM_1) {
|
||||
ESP_LOGI(TAG, "Enabling UART wakeup (press ENTER to exit light sleep)");
|
||||
ESP_ERROR_CHECK( uart_set_wakeup_threshold(CONFIG_ESP_CONSOLE_UART_NUM, 3) );
|
||||
ESP_ERROR_CHECK( esp_sleep_enable_uart_wakeup(CONFIG_ESP_CONSOLE_UART_NUM) );
|
||||
}
|
||||
fflush(stdout);
|
||||
uart_wait_tx_idle_polling(CONFIG_ESP_CONSOLE_UART_NUM);
|
||||
esp_light_sleep_start();
|
||||
|
||||
uint32_t causes = esp_sleep_get_wakeup_causes();
|
||||
if (causes & BIT(ESP_SLEEP_WAKEUP_UNDEFINED)) {
|
||||
ESP_LOGI(TAG, "Woke up from: unknown");
|
||||
printf("%" PRIx32 "\n", causes);
|
||||
return 0;
|
||||
}
|
||||
if (causes & BIT(ESP_SLEEP_WAKEUP_GPIO)) {
|
||||
ESP_LOGI(TAG, "Woke up from: GPIO");
|
||||
}
|
||||
if (causes & BIT(ESP_SLEEP_WAKEUP_UART)) {
|
||||
ESP_LOGI(TAG, "Woke up from: UART");
|
||||
}
|
||||
if (causes & BIT(ESP_SLEEP_WAKEUP_TIMER)) {
|
||||
ESP_LOGI(TAG, "Woke up from: timer");
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void register_light_sleep(void)
|
||||
{
|
||||
light_sleep_args.wakeup_time =
|
||||
arg_int0("t", "time", "<t>", "Wake up time, ms");
|
||||
light_sleep_args.wakeup_gpio_num =
|
||||
arg_intn(NULL, "io", "<n>", 0, 8,
|
||||
"If specified, wakeup using GPIO with given number");
|
||||
light_sleep_args.wakeup_gpio_level =
|
||||
arg_intn(NULL, "io_level", "<0|1>", 0, 8, "GPIO level to trigger wakeup");
|
||||
light_sleep_args.end = arg_end(3);
|
||||
|
||||
const esp_console_cmd_t cmd = {
|
||||
.command = "light_sleep",
|
||||
.help = "Enter light sleep mode. "
|
||||
"Two wakeup modes are supported: timer and GPIO. "
|
||||
"Multiple GPIO pins can be specified using pairs of "
|
||||
"'io' and 'io_level' arguments. "
|
||||
"Will also wake up on UART input.",
|
||||
.hint = NULL,
|
||||
.func = &light_sleep,
|
||||
.argtable = &light_sleep_args
|
||||
};
|
||||
ESP_ERROR_CHECK( esp_console_cmd_register(&cmd) );
|
||||
}
|
||||
#endif // SOC_LIGHT_SLEEP_SUPPORTED
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
// Register system functions
|
||||
void register_system(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
#
|
||||
# Component Makefile
|
||||
#
|
||||
# This Makefile should, at the very least, just include $(SDK_PATH)/Makefile. By default,
|
||||
# this will take the sources in the src/ directory, compile them and link them into
|
||||
# lib(subdirectory_name).a in the build directory. This behaviour is entirely configurable,
|
||||
# please read the SDK documents if you need to do this.
|
||||
#
|
||||
|
||||
COMPONENT_ADD_INCLUDEDIRS := .
|
||||
@@ -0,0 +1,3 @@
|
||||
idf_component_register(SRCS "main.c" "misc.c" "peer.c" "scli.c"
|
||||
PRIV_REQUIRES bt nvs_flash console esp_driver_uart cmd_system
|
||||
INCLUDE_DIRS ".")
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
menu "Example Configuration"
|
||||
|
||||
config EXAMPLE_PEER_ADDR
|
||||
string "Peer Address"
|
||||
default "ADDR_ANY"
|
||||
help
|
||||
Enter the peer address in aa:bb:cc:dd:ee:ff form to connect to a specific peripheral
|
||||
|
||||
config EXAMPLE_CONN_ITVL_MIN
|
||||
int "Minimum connection itvl"
|
||||
default 6
|
||||
help
|
||||
Set the minimum connection interval in 1.25msec units.
|
||||
Default 6 (7.5ms) for maximum throughput.
|
||||
|
||||
config EXAMPLE_CONN_ITVL_MAX
|
||||
int "Maximum connection itvl"
|
||||
default 6
|
||||
help
|
||||
Set the maximum connection interval in 1.25msec units.
|
||||
Default 6 (7.5ms) for maximum throughput.
|
||||
|
||||
config EXAMPLE_CONN_LATENCY
|
||||
int "Connection latency"
|
||||
default 0
|
||||
help
|
||||
Set the connection latency.
|
||||
|
||||
config EXAMPLE_CONN_TIMEOUT
|
||||
int "Supervision timeout"
|
||||
default 600
|
||||
help
|
||||
Set the supervision timeout in 10msec units.
|
||||
|
||||
config EXAMPLE_CONN_CE_LEN_MIN
|
||||
int "Minimum connection event length"
|
||||
default 12
|
||||
help
|
||||
Set the minimum connection event length in 0.625msec units.
|
||||
Default 12 (7.5ms) matches the minimum connection interval.
|
||||
|
||||
config EXAMPLE_CONN_CE_LEN_MAX
|
||||
int "Maximum connection event length"
|
||||
default 24
|
||||
help
|
||||
Set the maximum connection event length in 0.625msec units.
|
||||
Default 24 (15ms) allows controller to extend events when possible.
|
||||
|
||||
config EXAMPLE_EXTENDED_ADV
|
||||
bool
|
||||
depends on SOC_BLE_50_SUPPORTED && BT_NIMBLE_50_FEATURE_SUPPORT
|
||||
default y if SOC_ESP_NIMBLE_CONTROLLER
|
||||
select BT_NIMBLE_EXT_ADV
|
||||
prompt "Enable Extended Adv"
|
||||
help
|
||||
Use this option to enable extended advertising in the example.
|
||||
If this option is disabled, ensure config BT_NIMBLE_EXT_ADV is
|
||||
also disabled from Nimble stack menuconfig
|
||||
|
||||
endmenu
|
||||
@@ -0,0 +1,127 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#ifndef H_BLECENT_
|
||||
#define H_BLECENT_
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "modlog/modlog.h"
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
struct ble_hs_adv_fields;
|
||||
struct ble_gap_conn_desc;
|
||||
struct ble_hs_cfg;
|
||||
union ble_store_value;
|
||||
union ble_store_key;
|
||||
|
||||
#define BLECENT_SVC_ALERT_UUID 0x1811
|
||||
#define BLECENT_CHR_SUP_NEW_ALERT_CAT_UUID 0x2A47
|
||||
#define BLECENT_CHR_NEW_ALERT 0x2A46
|
||||
#define BLECENT_CHR_SUP_UNR_ALERT_CAT_UUID 0x2A48
|
||||
#define BLECENT_CHR_UNR_ALERT_STAT_UUID 0x2A45
|
||||
#define BLECENT_CHR_ALERT_NOT_CTRL_PT 0x2A44
|
||||
|
||||
#define PEER_ADDR_VAL_SIZE 6
|
||||
|
||||
/** Misc. */
|
||||
int peer_addr_parse(const char *addr_str, uint8_t addr[PEER_ADDR_VAL_SIZE]);
|
||||
void print_bytes(const uint8_t *bytes, int len);
|
||||
void print_mbuf(const struct os_mbuf *om);
|
||||
char *addr_str(const void *addr);
|
||||
void print_uuid(const ble_uuid_t *uuid);
|
||||
void print_conn_desc(const struct ble_gap_conn_desc *desc);
|
||||
void print_adv_fields(const struct ble_hs_adv_fields *fields);
|
||||
|
||||
/** Peer. */
|
||||
struct peer_dsc {
|
||||
SLIST_ENTRY(peer_dsc) next;
|
||||
struct ble_gatt_dsc dsc;
|
||||
};
|
||||
SLIST_HEAD(peer_dsc_list, peer_dsc);
|
||||
|
||||
struct peer_chr {
|
||||
SLIST_ENTRY(peer_chr) next;
|
||||
struct ble_gatt_chr chr;
|
||||
|
||||
struct peer_dsc_list dscs;
|
||||
};
|
||||
SLIST_HEAD(peer_chr_list, peer_chr);
|
||||
|
||||
#if MYNEWT_VAL(BLE_INCL_SVC_DISCOVERY) || MYNEWT_VAL(BLE_GATT_CACHING_INCLUDE_SERVICES)
|
||||
struct peer_incl_svc {
|
||||
SLIST_ENTRY(peer_incl_svc) next;
|
||||
struct ble_gatt_incl_svc svc;
|
||||
};
|
||||
SLIST_HEAD(peer_incl_svc_list, peer_incl_svc);
|
||||
#endif
|
||||
|
||||
struct peer_svc {
|
||||
SLIST_ENTRY(peer_svc) next;
|
||||
struct ble_gatt_svc svc;
|
||||
|
||||
#if MYNEWT_VAL(BLE_INCL_SVC_DISCOVERY) || MYNEWT_VAL(BLE_GATT_CACHING_INCLUDE_SERVICES)
|
||||
struct peer_incl_svc_list incl_svc;
|
||||
#endif
|
||||
struct peer_chr_list chrs;
|
||||
};
|
||||
SLIST_HEAD(peer_svc_list, peer_svc);
|
||||
|
||||
struct peer;
|
||||
typedef void peer_disc_fn(const struct peer *peer, int status, void *arg);
|
||||
|
||||
struct peer {
|
||||
SLIST_ENTRY(peer) next;
|
||||
|
||||
uint16_t conn_handle;
|
||||
|
||||
/** List of discovered GATT services. */
|
||||
struct peer_svc_list svcs;
|
||||
|
||||
/** Keeps track of where we are in the service discovery process. */
|
||||
uint16_t disc_prev_chr_val;
|
||||
struct peer_svc *cur_svc;
|
||||
|
||||
/** Callback that gets executed when service discovery completes. */
|
||||
peer_disc_fn *disc_cb;
|
||||
void *disc_cb_arg;
|
||||
};
|
||||
|
||||
int peer_disc_all(uint16_t conn_handle, peer_disc_fn *disc_cb,
|
||||
void *disc_cb_arg);
|
||||
const struct peer_dsc *
|
||||
peer_dsc_find_uuid(const struct peer *peer, const ble_uuid_t *svc_uuid,
|
||||
const ble_uuid_t *chr_uuid, const ble_uuid_t *dsc_uuid);
|
||||
const struct peer_chr *
|
||||
peer_chr_find_uuid(const struct peer *peer, const ble_uuid_t *svc_uuid,
|
||||
const ble_uuid_t *chr_uuid);
|
||||
const struct peer_svc *
|
||||
peer_svc_find_uuid(const struct peer *peer, const ble_uuid_t *uuid);
|
||||
int peer_delete(uint16_t conn_handle);
|
||||
int peer_add(uint16_t conn_handle);
|
||||
#if MYNEWT_VAL(BLE_INCL_SVC_DISCOVERY) || MYNEWT_VAL(BLE_GATT_CACHING_INCLUDE_SERVICES)
|
||||
int peer_init(int max_peers, int max_svcs, int max_incl_svcs, int max_chrs, int max_dscs);
|
||||
#else
|
||||
int peer_init(int max_peers, int max_svcs, int max_chrs, int max_dscs);
|
||||
#endif
|
||||
struct peer *
|
||||
peer_find(uint16_t conn_handle);
|
||||
/* Console */
|
||||
int scli_init(void);
|
||||
void ble_register_cli(void);
|
||||
int scli_receive_key(int key[6]);
|
||||
int cli_receive_key(int key[6]);
|
||||
int scli_receive_yesno(bool *key);
|
||||
void scli_reset_queue(void);
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,212 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2015-2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include "host/ble_hs.h"
|
||||
#include "host/ble_uuid.h"
|
||||
#include "gattc.h"
|
||||
|
||||
int
|
||||
peer_addr_parse(const char *addr_str, uint8_t addr[PEER_ADDR_VAL_SIZE])
|
||||
{
|
||||
if (addr_str == NULL) {
|
||||
return 0;
|
||||
}
|
||||
return sscanf(addr_str, "%hhx:%hhx:%hhx:%hhx:%hhx:%hhx",
|
||||
&addr[5], &addr[4], &addr[3],
|
||||
&addr[2], &addr[1], &addr[0]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility function to log an array of bytes.
|
||||
*/
|
||||
void
|
||||
print_bytes(const uint8_t *bytes, int len)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < len; i++) {
|
||||
MODLOG_DFLT(DEBUG, "%s0x%02x", i != 0 ? ":" : "", bytes[i]);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
print_mbuf(const struct os_mbuf *om)
|
||||
{
|
||||
int colon, i;
|
||||
|
||||
colon = 0;
|
||||
while (om != NULL) {
|
||||
if (colon) {
|
||||
MODLOG_DFLT(INFO, ":");
|
||||
} else {
|
||||
colon = 1;
|
||||
}
|
||||
for (i = 0; i < om->om_len; i++) {
|
||||
MODLOG_DFLT(INFO, "%s0x%02x", i != 0 ? ":" : "", om->om_data[i]);
|
||||
}
|
||||
om = SLIST_NEXT(om, om_next);
|
||||
}
|
||||
}
|
||||
|
||||
char *
|
||||
addr_str(const void *addr)
|
||||
{
|
||||
static char buf[6 * 2 + 5 + 1];
|
||||
const uint8_t *u8p;
|
||||
|
||||
u8p = addr;
|
||||
sprintf(buf, "%02x:%02x:%02x:%02x:%02x:%02x",
|
||||
u8p[5], u8p[4], u8p[3], u8p[2], u8p[1], u8p[0]);
|
||||
|
||||
return buf;
|
||||
}
|
||||
|
||||
void
|
||||
print_uuid(const ble_uuid_t *uuid)
|
||||
{
|
||||
char buf[BLE_UUID_STR_LEN];
|
||||
|
||||
MODLOG_DFLT(DEBUG, "%s", ble_uuid_to_str(uuid, buf));
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs information about a connection to the console.
|
||||
*/
|
||||
void
|
||||
print_conn_desc(const struct ble_gap_conn_desc *desc)
|
||||
{
|
||||
MODLOG_DFLT(DEBUG, "handle=%d our_ota_addr_type=%d our_ota_addr=%s ",
|
||||
desc->conn_handle, desc->our_ota_addr.type,
|
||||
addr_str(desc->our_ota_addr.val));
|
||||
MODLOG_DFLT(DEBUG, "our_id_addr_type=%d our_id_addr=%s ",
|
||||
desc->our_id_addr.type, addr_str(desc->our_id_addr.val));
|
||||
MODLOG_DFLT(DEBUG, "peer_ota_addr_type=%d peer_ota_addr=%s ",
|
||||
desc->peer_ota_addr.type, addr_str(desc->peer_ota_addr.val));
|
||||
MODLOG_DFLT(DEBUG, "peer_id_addr_type=%d peer_id_addr=%s ",
|
||||
desc->peer_id_addr.type, addr_str(desc->peer_id_addr.val));
|
||||
MODLOG_DFLT(DEBUG, "conn_itvl=%d conn_latency=%d supervision_timeout=%d "
|
||||
"encrypted=%d authenticated=%d bonded=%d",
|
||||
desc->conn_itvl, desc->conn_latency,
|
||||
desc->supervision_timeout,
|
||||
desc->sec_state.encrypted,
|
||||
desc->sec_state.authenticated,
|
||||
desc->sec_state.bonded);
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
print_adv_fields(const struct ble_hs_adv_fields *fields)
|
||||
{
|
||||
char s[BLE_HS_ADV_MAX_SZ];
|
||||
const uint8_t *u8p;
|
||||
int i;
|
||||
|
||||
if (fields->flags != 0) {
|
||||
MODLOG_DFLT(DEBUG, " flags=0x%02x\n", fields->flags);
|
||||
}
|
||||
|
||||
if (fields->uuids16 != NULL) {
|
||||
MODLOG_DFLT(DEBUG, " uuids16(%scomplete)=",
|
||||
fields->uuids16_is_complete ? "" : "in");
|
||||
for (i = 0; i < fields->num_uuids16; i++) {
|
||||
print_uuid(&fields->uuids16[i].u);
|
||||
MODLOG_DFLT(DEBUG, " ");
|
||||
}
|
||||
MODLOG_DFLT(DEBUG, "\n");
|
||||
}
|
||||
|
||||
if (fields->uuids32 != NULL) {
|
||||
MODLOG_DFLT(DEBUG, " uuids32(%scomplete)=",
|
||||
fields->uuids32_is_complete ? "" : "in");
|
||||
for (i = 0; i < fields->num_uuids32; i++) {
|
||||
print_uuid(&fields->uuids32[i].u);
|
||||
MODLOG_DFLT(DEBUG, " ");
|
||||
}
|
||||
MODLOG_DFLT(DEBUG, "\n");
|
||||
}
|
||||
|
||||
if (fields->uuids128 != NULL) {
|
||||
MODLOG_DFLT(DEBUG, " uuids128(%scomplete)=",
|
||||
fields->uuids128_is_complete ? "" : "in");
|
||||
for (i = 0; i < fields->num_uuids128; i++) {
|
||||
print_uuid(&fields->uuids128[i].u);
|
||||
MODLOG_DFLT(DEBUG, " ");
|
||||
}
|
||||
MODLOG_DFLT(DEBUG, "\n");
|
||||
}
|
||||
|
||||
if (fields->name != NULL) {
|
||||
size_t copy_len = fields->name_len;
|
||||
if (copy_len >= sizeof(s)) {
|
||||
copy_len = sizeof(s) - 1;
|
||||
}
|
||||
memcpy(s, fields->name, copy_len);
|
||||
s[copy_len] = '\0';
|
||||
MODLOG_DFLT(DEBUG, " name(%scomplete)=%s\n",
|
||||
fields->name_is_complete ? "" : "in", s);
|
||||
}
|
||||
|
||||
if (fields->tx_pwr_lvl_is_present) {
|
||||
MODLOG_DFLT(DEBUG, " tx_pwr_lvl=%d\n", fields->tx_pwr_lvl);
|
||||
}
|
||||
|
||||
if (fields->slave_itvl_range != NULL) {
|
||||
MODLOG_DFLT(DEBUG, " slave_itvl_range=");
|
||||
print_bytes(fields->slave_itvl_range, BLE_HS_ADV_SLAVE_ITVL_RANGE_LEN);
|
||||
MODLOG_DFLT(DEBUG, "\n");
|
||||
}
|
||||
|
||||
if (fields->svc_data_uuid16 != NULL) {
|
||||
MODLOG_DFLT(DEBUG, " svc_data_uuid16=");
|
||||
print_bytes(fields->svc_data_uuid16, fields->svc_data_uuid16_len);
|
||||
MODLOG_DFLT(DEBUG, "\n");
|
||||
}
|
||||
|
||||
if (fields->public_tgt_addr != NULL) {
|
||||
MODLOG_DFLT(DEBUG, " public_tgt_addr=");
|
||||
u8p = fields->public_tgt_addr;
|
||||
for (i = 0; i < fields->num_public_tgt_addrs; i++) {
|
||||
MODLOG_DFLT(DEBUG, "public_tgt_addr=%s ", addr_str(u8p));
|
||||
u8p += BLE_HS_ADV_PUBLIC_TGT_ADDR_ENTRY_LEN;
|
||||
}
|
||||
MODLOG_DFLT(DEBUG, "\n");
|
||||
}
|
||||
|
||||
if (fields->appearance_is_present) {
|
||||
MODLOG_DFLT(DEBUG, " appearance=0x%04x\n", fields->appearance);
|
||||
}
|
||||
|
||||
if (fields->adv_itvl_is_present) {
|
||||
MODLOG_DFLT(DEBUG, " adv_itvl=0x%04x\n", fields->adv_itvl);
|
||||
}
|
||||
|
||||
if (fields->svc_data_uuid32 != NULL) {
|
||||
MODLOG_DFLT(DEBUG, " svc_data_uuid32=");
|
||||
print_bytes(fields->svc_data_uuid32, fields->svc_data_uuid32_len);
|
||||
MODLOG_DFLT(DEBUG, "\n");
|
||||
}
|
||||
|
||||
if (fields->svc_data_uuid128 != NULL) {
|
||||
MODLOG_DFLT(DEBUG, " svc_data_uuid128=");
|
||||
print_bytes(fields->svc_data_uuid128, fields->svc_data_uuid128_len);
|
||||
MODLOG_DFLT(DEBUG, "\n");
|
||||
}
|
||||
|
||||
if (fields->uri != NULL) {
|
||||
MODLOG_DFLT(DEBUG, " uri=");
|
||||
print_bytes(fields->uri, fields->uri_len);
|
||||
MODLOG_DFLT(DEBUG, "\n");
|
||||
}
|
||||
|
||||
if (fields->mfg_data != NULL) {
|
||||
MODLOG_DFLT(DEBUG, " mfg_data=");
|
||||
print_bytes(fields->mfg_data, fields->mfg_data_len);
|
||||
MODLOG_DFLT(DEBUG, "\n");
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,263 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2015-2025 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <ctype.h>
|
||||
#include "esp_log.h"
|
||||
#include <string.h>
|
||||
#include <strings.h>
|
||||
#include <esp_log.h>
|
||||
#include <esp_console.h>
|
||||
#include "esp_vfs_dev.h"
|
||||
#include "linenoise/linenoise.h"
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/task.h>
|
||||
#include <freertos/queue.h>
|
||||
#include <driver/uart.h>
|
||||
|
||||
#define BLE_RX_TIMEOUT (30000 / portTICK_PERIOD_MS)
|
||||
#define BLE_RX_PARAM (10000 / portTICK_PERIOD_MS)
|
||||
#define YES_NO_PARAM (5000 / portTICK_PERIOD_MS)
|
||||
|
||||
static QueueHandle_t cli_handle;
|
||||
|
||||
#define CLI_CONN_PARAM_COUNT 6
|
||||
|
||||
#define CLI_MSG_TYPE_CONN_PARAM 1
|
||||
#define CLI_MSG_TYPE_MTU 2
|
||||
#define CLI_MSG_TYPE_THROUGHPUT 3
|
||||
#define CLI_MSG_TYPE_YESNO 4
|
||||
|
||||
struct cli_msg {
|
||||
int type;
|
||||
union {
|
||||
int conn_param[CLI_CONN_PARAM_COUNT];
|
||||
int mtu;
|
||||
int key[3];
|
||||
bool yes;
|
||||
} data;
|
||||
};
|
||||
|
||||
#define CONSOLE_PROMPT_LEN_MAX (32)
|
||||
|
||||
typedef enum {
|
||||
CONSOLE_REPL_STATE_DEINIT,
|
||||
CONSOLE_REPL_STATE_INIT,
|
||||
CONSOLE_REPL_STATE_START,
|
||||
} repl_state_t;
|
||||
|
||||
static int conn_param_handler(int argc, char *argv[])
|
||||
{
|
||||
ESP_LOGI("Conn param Arguments entered", "%d", argc);
|
||||
if (argc != 7) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
struct cli_msg msg = {
|
||||
.type = CLI_MSG_TYPE_CONN_PARAM,
|
||||
};
|
||||
|
||||
sscanf(argv[1], "%d", &msg.data.conn_param[0]);
|
||||
sscanf(argv[2], "%d", &msg.data.conn_param[1]);
|
||||
sscanf(argv[3], "%d", &msg.data.conn_param[2]);
|
||||
sscanf(argv[4], "%d", &msg.data.conn_param[3]);
|
||||
sscanf(argv[5], "%d", &msg.data.conn_param[4]);
|
||||
sscanf(argv[6], "%d", &msg.data.conn_param[5]);
|
||||
|
||||
ESP_LOGI("You entered", "%s %d %d %d %d %d %d", argv[0], msg.data.conn_param[0], msg.data.conn_param[1],
|
||||
msg.data.conn_param[2], msg.data.conn_param[3], msg.data.conn_param[4], msg.data.conn_param[5]);
|
||||
if (cli_handle == NULL) {
|
||||
ESP_LOGE("CLI", "Queue not initialized");
|
||||
return -1;
|
||||
}
|
||||
xQueueSend(cli_handle, &msg, 500 / portTICK_PERIOD_MS);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int conn_mtu_handler(int argc, char *argv[])
|
||||
{
|
||||
int ret;
|
||||
int mtu;
|
||||
ESP_LOGI("MTU Arguments entered", "%d", argc);
|
||||
if (argc != 2) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
ret = sscanf(argv[1], "%d", &mtu);
|
||||
if (ret != 1) {
|
||||
return -1;
|
||||
}
|
||||
struct cli_msg msg = {
|
||||
.type = CLI_MSG_TYPE_MTU,
|
||||
.data.mtu = mtu,
|
||||
};
|
||||
if (cli_handle) {
|
||||
xQueueSend(cli_handle, &msg, 500 / portTICK_PERIOD_MS);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int throughput_demo_handler(int argc, char *argv[])
|
||||
{
|
||||
char pkey[8];
|
||||
struct cli_msg msg = {
|
||||
.type = CLI_MSG_TYPE_THROUGHPUT,
|
||||
};
|
||||
|
||||
if (argc != 4) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
sscanf(argv[1], "%7s", pkey);
|
||||
|
||||
if (strcmp(pkey, "read") == 0) {
|
||||
msg.data.key[0] = 1;
|
||||
} else if (strcmp(pkey, "write") == 0) {
|
||||
msg.data.key[0] = 2;
|
||||
} else if (strcmp(pkey, "notify") == 0) {
|
||||
msg.data.key[0] = 3;
|
||||
} else {
|
||||
msg.data.key[0] = 0;
|
||||
}
|
||||
|
||||
sscanf(argv[2], "%d", &msg.data.key[1]);
|
||||
sscanf(argv[3], "%d", &msg.data.key[2]);
|
||||
ESP_LOGI("Throughput demo handler", "%s %s %d %d", argv[0], argv[1], msg.data.key[1], msg.data.key[2]);
|
||||
xQueueSend(cli_handle, &msg, 500 / portTICK_PERIOD_MS);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int yesno_handler(int argc, char *argv[])
|
||||
{
|
||||
char yesno[4];
|
||||
bool yes;
|
||||
|
||||
if (argc != 2) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
sscanf(argv[1], "%3s", yesno);
|
||||
|
||||
if (strcmp(yesno, "Yes") == 0 || strcmp(yesno, "YES") == 0 || strcmp(yesno, "yes") == 0) {
|
||||
yes = 1;
|
||||
} else if (strcmp(yesno, "No") == 0 || strcmp(yesno, "NO") == 0 || strcmp(yesno, "no") == 0) {
|
||||
yes = 0;
|
||||
} else {
|
||||
yes = 0; /* invalid input */
|
||||
}
|
||||
|
||||
ESP_LOGI("User entered", "%s %s", argv[0], yesno);
|
||||
/* Send as 24-byte buffer to match queue item size */
|
||||
uint8_t yesno_buf[24] = {0};
|
||||
yesno_buf[0] = (uint8_t)yes;
|
||||
if (cli_handle) {
|
||||
xQueueSend(cli_handle, yesno_buf, 500 / portTICK_PERIOD_MS);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int scli_receive_yesno(bool *console_key)
|
||||
{
|
||||
/* Receive into temporary 24-byte buffer to match queue item size,
|
||||
* then extract bool value to prevent buffer overflow */
|
||||
uint8_t temp_buf[24];
|
||||
int ret = xQueueReceive(cli_handle, temp_buf, YES_NO_PARAM);
|
||||
if (ret == pdPASS) {
|
||||
*console_key = (bool)temp_buf[0]; /* Extract first byte as bool */
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
int scli_receive_key(int console_key[6])
|
||||
{
|
||||
struct cli_msg msg;
|
||||
if (xQueueReceive(cli_handle, &msg, BLE_RX_PARAM) != pdTRUE) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
switch (msg.type) {
|
||||
case CLI_MSG_TYPE_MTU:
|
||||
console_key[0] = msg.data.mtu;
|
||||
return 1;
|
||||
case CLI_MSG_TYPE_CONN_PARAM:
|
||||
memcpy(console_key, msg.data.conn_param,
|
||||
sizeof(msg.data.conn_param));
|
||||
return 1;
|
||||
case CLI_MSG_TYPE_THROUGHPUT:
|
||||
memcpy(console_key, msg.data.key, sizeof(msg.data.key));
|
||||
return 1;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
int cli_receive_key(int console_key[6])
|
||||
{
|
||||
struct cli_msg msg;
|
||||
if (xQueueReceive(cli_handle, &msg, BLE_RX_TIMEOUT) != pdTRUE) {
|
||||
return 0;
|
||||
}
|
||||
switch (msg.type) {
|
||||
case CLI_MSG_TYPE_CONN_PARAM:
|
||||
memcpy(console_key, msg.data.conn_param, sizeof(msg.data.conn_param));
|
||||
return 1;
|
||||
case CLI_MSG_TYPE_THROUGHPUT:
|
||||
memcpy(console_key, msg.data.key, sizeof(msg.data.key));
|
||||
return 1;
|
||||
case CLI_MSG_TYPE_MTU:
|
||||
console_key[0] = msg.data.mtu;
|
||||
return 1;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
void scli_reset_queue(void)
|
||||
{
|
||||
xQueueReset(cli_handle);
|
||||
}
|
||||
|
||||
static esp_console_cmd_t cmds[] = {
|
||||
{
|
||||
.command = "conn",
|
||||
.help = "Set connection parameters: min itvl, max itvl, latency,"
|
||||
"supervision timeout, min CE, max CE",
|
||||
.func = &conn_param_handler,
|
||||
},
|
||||
{
|
||||
.command = "MTU",
|
||||
.help = "Set MTU value",
|
||||
.func = &conn_mtu_handler,
|
||||
},
|
||||
{
|
||||
.command = "throughput",
|
||||
.help = "Enter read/write/notify and time (in seconds)",
|
||||
.func = &throughput_demo_handler,
|
||||
},
|
||||
{
|
||||
.command = "Insert",
|
||||
.help = "Enter Insert Yes for YES or Insert No for NO",
|
||||
.func = &yesno_handler,
|
||||
},
|
||||
};
|
||||
|
||||
void ble_register_cli(void)
|
||||
{
|
||||
int cmds_num = sizeof(cmds) / sizeof(esp_console_cmd_t);
|
||||
int i;
|
||||
for (i = 0; i < cmds_num; i++) {
|
||||
esp_console_cmd_register(&cmds[i]);
|
||||
}
|
||||
|
||||
cli_handle = xQueueCreate(1, sizeof(struct cli_msg));
|
||||
if (cli_handle == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
ESP_LOGI("CLI", "BLE CLI registered ");
|
||||
return;
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
# Override some defaults so BT stack is enabled
|
||||
# in this example and some misc buffer sizes are increased
|
||||
|
||||
#
|
||||
# BT config (universal across all ESP32 variants)
|
||||
#
|
||||
CONFIG_BT_ENABLED=y
|
||||
CONFIG_BT_BLUEDROID_ENABLED=n
|
||||
CONFIG_BT_NIMBLE_ENABLED=y
|
||||
|
||||
# === BT 5.0 / 2M PHY support ===
|
||||
# Ignored automatically on classic ESP32 which lacks BT 5.0
|
||||
CONFIG_BT_NIMBLE_EXT_ADV=n
|
||||
CONFIG_BT_NIMBLE_50_FEATURE_SUPPORT=y
|
||||
CONFIG_EXAMPLE_EXTENDED_ADV=n
|
||||
|
||||
# === ATT / GATT ===
|
||||
CONFIG_BT_NIMBLE_ATT_PREFERRED_MTU=512
|
||||
|
||||
# === Memory Pool Tuning ===
|
||||
CONFIG_BT_NIMBLE_MAX_CONNECTIONS=1
|
||||
CONFIG_BT_NIMBLE_MSYS_1_BLOCK_COUNT=50
|
||||
CONFIG_BT_NIMBLE_MSYS_2_BLOCK_SIZE=592
|
||||
CONFIG_BT_NIMBLE_MSYS_2_BLOCK_COUNT=40
|
||||
|
||||
# Increase controller-to-host buffers
|
||||
CONFIG_BT_NIMBLE_TRANSPORT_ACL_FROM_LL_COUNT=40
|
||||
CONFIG_BT_NIMBLE_TRANSPORT_EVT_SIZE=255
|
||||
|
||||
# === Connection Parameters (write/notify optimal; code switches dynamically for reads) ===
|
||||
CONFIG_EXAMPLE_CONN_ITVL_MIN=24
|
||||
CONFIG_EXAMPLE_CONN_ITVL_MAX=40
|
||||
|
||||
# Disable NimBLE INFO logging for throughput tests
|
||||
CONFIG_BT_NIMBLE_LOG_LEVEL_WARNING=y
|
||||
CONFIG_BT_NIMBLE_LOG_LEVEL=2
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
# === High-Performance Tuning ===
|
||||
# This config is only applied when building for the ESP32-C6 target
|
||||
# It is necessary for the C6 to reach 750+ kbps and 1.3+ Mbps throughput.
|
||||
CONFIG_COMPILER_OPTIMIZATION_PERF=y
|
||||
CONFIG_FREERTOS_HZ=1000
|
||||
@@ -0,0 +1,8 @@
|
||||
# The following lines of boilerplate have to be in your project's
|
||||
# CMakeLists in this exact order for cmake to work correctly
|
||||
cmake_minimum_required(VERSION 3.22)
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
# "Trim" the build. Include the minimal set of components, main, and anything it depends on.
|
||||
idf_build_set_property(MINIMAL_BUILD ON)
|
||||
project(bleprph_throughput)
|
||||
@@ -0,0 +1,69 @@
|
||||
| Supported Targets | ESP32 | ESP32-C2 | ESP32-C3 | ESP32-C5 | ESP32-C6 | ESP32-C61 | ESP32-H2 | ESP32-S3 |
|
||||
| ----------------- | ----- | -------- | -------- | -------- | -------- | --------- | -------- | -------- |
|
||||
|
||||
# Throughput bleprph Example
|
||||
|
||||
(See the README.md file in the upper level 'examples' directory for more information about examples.)
|
||||
|
||||
`bleprph_throughput` demonstrates server side implementation required for NimBLE throughput example. It has characteristics supporting READ, WRITE and NOTIFY (`THRPT_LONG_CHR_READ_WRITE`, `THRPT_CHR_READ_WRITE`, `THRPT_CHR_NOTIFY`). The read characteristic holds 510 bytes (`READ_THROUGHPUT_PAYLOAD`) and the write characteristic accepts up to 509 bytes (`WRITE_THROUGHPUT_PAYLOAD`). Notifications are sent with a 509-byte payload (`NOTIFY_THROUGHPUT_PAYLOAD`). These sizes are chosen to maximize ATT PDU utilization with the default MTU of 512.
|
||||
|
||||
`bleprph_throughput` uses the `nimble` component as BLE host.
|
||||
|
||||
### Build and Flash
|
||||
|
||||
Build the project and flash it to the board, then run monitor tool to view serial output:
|
||||
|
||||
```
|
||||
idf.py -p PORT flash monitor
|
||||
```
|
||||
|
||||
(To exit the serial monitor, type ``Ctrl-]``.)
|
||||
|
||||
See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects.
|
||||
|
||||
## Example Output
|
||||
|
||||
```
|
||||
I (573) BTDM_INIT: BT controller compile version [b73c48e]
|
||||
I (573) system_api: Base MAC address is not set
|
||||
I (573) system_api: read default base MAC address from EFUSE
|
||||
I (663) phy: phy_version: 4500, 0cd6843, Sep 17 2020, 15:37:07, 0, 0
|
||||
I (903) bleprph_throughput: BLE Host Task Started
|
||||
I (933) bleprph_throughput: Device Address:
|
||||
I (933) bleprph_throughput: 3c:71:bf:99:38:7a
|
||||
GAP procedure initiated: advertise; disc_mode=2 adv_channel_map=0 own_addr_type=0 adv_filter_policy=0 adv_itvl_min=0 adv_itvl_max=0
|
||||
I (1403) bleprph_throughput: connection established; status = 0
|
||||
I (1433) bleprph_throughput: mtu update event; conn_handle = 0 mtu = 512
|
||||
|
||||
```
|
||||
|
||||
In a separate terminal flash and run corresponding `blecent_throughput` for full fledged throughput demo.
|
||||
|
||||
If at the `blecent_throughput` side `notify` is selected as GATT operation, then below console output can be observed:
|
||||
|
||||
```
|
||||
I (23853) bleprph_throughput: subscribe event; cur_notify=1; value handle; val_handle = 14
|
||||
GATT procedure initiated: notify; att_handle=14
|
||||
GATT procedure initiated: notify; att_handle=14
|
||||
GATT procedure initiated: notify; att_handle=14
|
||||
GATT procedure initiated: notify; att_handle=14
|
||||
.
|
||||
.
|
||||
.
|
||||
|
||||
*********************************
|
||||
I (83943) bleprph_throughput: Notify throughput = 160466 bps, count = 2407
|
||||
|
||||
*********************************
|
||||
I (83943) bleprph_throughput: Notification test completed for stipulated time of 60 sec
|
||||
|
||||
```
|
||||
|
||||
> Here, bps is bits per second; count is number of Notifications successfully sent.
|
||||
|
||||
## Example Scope
|
||||
|
||||
This demo example along with `blecent_throughput` demonstrates stable GATT read/write and notify operations at high throughput. For notifications, the peripheral uses a pipelined approach: a counting semaphore (max 100) allows multiple notifications to be queued simultaneously (pipeline depth of 15). This enables the BLE controller to pack multiple PDUs into each connection event for maximum throughput.
|
||||
|
||||
Notifications are sent in `os_mbufs` packets. The example is configured with `CONFIG_BT_NIMBLE_MSYS_1_BLOCK_COUNT=50` to provide sufficient buffer space. If mbuf exhaustion occurs during continuous transfer, the app yields briefly to allow the NimBLE host stack to free completed `mbuf chains` before retrying.
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
idf_component_register(SRCS "main.c" "gatt_svr.c"
|
||||
PRIV_REQUIRES bt nvs_flash
|
||||
INCLUDE_DIRS ".")
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
menu "Example Configuration"
|
||||
|
||||
config EXAMPLE_EXTENDED_ADV
|
||||
bool
|
||||
depends on SOC_BLE_50_SUPPORTED && BT_NIMBLE_50_FEATURE_SUPPORT
|
||||
default y if SOC_ESP_NIMBLE_CONTROLLER
|
||||
select BT_NIMBLE_EXT_ADV
|
||||
prompt "Enable Extended Adv"
|
||||
help
|
||||
Use this option to enable extended advertising in the example.
|
||||
If this option is disabled, ensure config BT_NIMBLE_EXT_ADV is
|
||||
also disabled from Nimble stack menuconfig
|
||||
|
||||
endmenu
|
||||
@@ -0,0 +1,244 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2015-2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include "host/ble_hs.h"
|
||||
#include "host/ble_uuid.h"
|
||||
#include "services/gap/ble_svc_gap.h"
|
||||
#include "services/gatt/ble_svc_gatt.h"
|
||||
#include "gatts_sens.h"
|
||||
#include "esp_log.h"
|
||||
|
||||
/* 0000xxxx-8c26-476f-89a7-a108033a69c7 */
|
||||
#define THRPT_UUID_DECLARE(uuid16) \
|
||||
((const ble_uuid_t *) (&(ble_uuid128_t) BLE_UUID128_INIT( \
|
||||
0xc7, 0x69, 0x3a, 0x03, 0x08, 0xa1, 0xa7, 0x89, \
|
||||
0x6f, 0x47, 0x26, 0x8c, uuid16, uuid16 >> 8, 0x00, 0x00 \
|
||||
)))
|
||||
|
||||
/* 0000xxxx-8c26-476f-89a7-a108033a69c6 */
|
||||
#define THRPT_UUID_DECLARE_ALT(uuid16) \
|
||||
((const ble_uuid_t *) (&(ble_uuid128_t) BLE_UUID128_INIT( \
|
||||
0xc6, 0x69, 0x3a, 0x03, 0x08, 0xa1, 0xa7, 0x89, \
|
||||
0x6f, 0x47, 0x26, 0x8c, uuid16, uuid16 >> 8, 0x00, 0x00 \
|
||||
)))
|
||||
|
||||
#define THRPT_SVC 0x0001
|
||||
#define THRPT_CHR_READ_WRITE 0x0006
|
||||
#define THRPT_CHR_NOTIFY 0x000a
|
||||
#define THRPT_LONG_CHR_READ_WRITE 0x000b
|
||||
|
||||
#define READ_THROUGHPUT_PAYLOAD 497 /* 502 bytes ACL -> 2x 251 LL packets exactly (497 + 1 Read Rsp + 4 L2CAP) */
|
||||
#define WRITE_THROUGHPUT_PAYLOAD 495 /* 502 bytes ACL -> 2x 251 LL packets exactly (495 + 3 Write Cmd + 4 L2CAP) */
|
||||
|
||||
static const char *tag = "bleprph_throughput";
|
||||
|
||||
static uint8_t gatt_svr_thrpt_static_long_val[READ_THROUGHPUT_PAYLOAD];
|
||||
static uint8_t gatt_svr_thrpt_static_short_val[WRITE_THROUGHPUT_PAYLOAD];
|
||||
uint16_t notify_handle;
|
||||
|
||||
static int
|
||||
gatt_svr_read_write_long_test(uint16_t conn_handle, uint16_t attr_handle,
|
||||
struct ble_gatt_access_ctxt *ctxt,
|
||||
void *arg);
|
||||
|
||||
static const struct ble_gatt_svc_def gatts_test_svcs[] = {
|
||||
{
|
||||
/*** Service: THRPT test. */
|
||||
.type = BLE_GATT_SVC_TYPE_PRIMARY,
|
||||
.uuid = THRPT_UUID_DECLARE(THRPT_SVC),
|
||||
.characteristics = (struct ble_gatt_chr_def[])
|
||||
{ {
|
||||
.uuid = THRPT_UUID_DECLARE(THRPT_CHR_READ_WRITE),
|
||||
.access_cb = gatt_svr_read_write_long_test,
|
||||
.flags = BLE_GATT_CHR_F_READ |
|
||||
BLE_GATT_CHR_F_WRITE,
|
||||
|
||||
}, {
|
||||
.uuid = THRPT_UUID_DECLARE(THRPT_CHR_NOTIFY),
|
||||
.access_cb = gatt_svr_read_write_long_test,
|
||||
.val_handle = ¬ify_handle,
|
||||
.flags = BLE_GATT_CHR_F_NOTIFY,
|
||||
}, {
|
||||
.uuid = THRPT_UUID_DECLARE(THRPT_LONG_CHR_READ_WRITE),
|
||||
.access_cb = gatt_svr_read_write_long_test,
|
||||
.flags = BLE_GATT_CHR_F_WRITE |
|
||||
BLE_GATT_CHR_F_READ,
|
||||
}, {
|
||||
0, /* No more characteristics in this service. */
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
0, /* No more services. */
|
||||
},
|
||||
};
|
||||
|
||||
static uint16_t
|
||||
extract_uuid16_from_thrpt_uuid128(const ble_uuid_t *uuid)
|
||||
{
|
||||
const uint8_t *u8ptr;
|
||||
uint16_t uuid16;
|
||||
|
||||
u8ptr = BLE_UUID128(uuid)->value;
|
||||
uuid16 = u8ptr[12];
|
||||
uuid16 |= (uint16_t)u8ptr[13] << 8;
|
||||
return uuid16;
|
||||
}
|
||||
|
||||
static int
|
||||
gatt_svr_chr_write(uint16_t conn_handle, uint16_t attr_handle,
|
||||
struct os_mbuf *om, uint16_t min_len, uint16_t max_len,
|
||||
void *dst, uint16_t *len)
|
||||
{
|
||||
uint16_t om_len;
|
||||
int rc;
|
||||
|
||||
om_len = OS_MBUF_PKTLEN(om);
|
||||
if (om_len < min_len || om_len > max_len) {
|
||||
return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
|
||||
}
|
||||
|
||||
rc = ble_hs_mbuf_to_flat(om, dst, max_len, len);
|
||||
if (rc != 0) {
|
||||
return BLE_ATT_ERR_UNLIKELY;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
gatt_svr_read_write_long_test(uint16_t conn_handle, uint16_t attr_handle,
|
||||
struct ble_gatt_access_ctxt *ctxt,
|
||||
void *arg)
|
||||
{
|
||||
uint16_t uuid16;
|
||||
int rc;
|
||||
|
||||
uuid16 = extract_uuid16_from_thrpt_uuid128(ctxt->chr->uuid);
|
||||
assert(uuid16 != 0);
|
||||
|
||||
switch (uuid16) {
|
||||
case THRPT_LONG_CHR_READ_WRITE:
|
||||
if (ctxt->op == BLE_GATT_ACCESS_OP_WRITE_CHR) {
|
||||
rc = gatt_svr_chr_write(conn_handle, attr_handle,
|
||||
ctxt->om, 0,
|
||||
sizeof gatt_svr_thrpt_static_long_val,
|
||||
&gatt_svr_thrpt_static_long_val, NULL);
|
||||
return rc;
|
||||
} else if (ctxt->op == BLE_GATT_ACCESS_OP_READ_CHR) {
|
||||
gatt_svr_thrpt_static_long_val[0] = rand();
|
||||
rc = os_mbuf_append(ctxt->om, &gatt_svr_thrpt_static_long_val,
|
||||
sizeof gatt_svr_thrpt_static_long_val);
|
||||
return rc == 0 ? 0 : BLE_ATT_ERR_INSUFFICIENT_RES;
|
||||
}
|
||||
return 0;
|
||||
|
||||
case THRPT_CHR_READ_WRITE:
|
||||
if (ctxt->op == BLE_GATT_ACCESS_OP_WRITE_CHR) {
|
||||
uint16_t om_len = OS_MBUF_PKTLEN(ctxt->om);
|
||||
/* If the central sends exactly 1 byte, it is a PHY control command */
|
||||
if (om_len == 1) {
|
||||
uint8_t requested_phy;
|
||||
os_mbuf_copydata(ctxt->om, 0, 1, &requested_phy);
|
||||
rc = 0;
|
||||
if (requested_phy == 0) { /* 0 = PHY_1M */
|
||||
rc = ble_gap_set_prefered_le_phy(conn_handle, BLE_HCI_LE_PHY_1M_PREF_MASK, BLE_HCI_LE_PHY_1M_PREF_MASK, 0);
|
||||
ESP_LOGI(tag, "Central requested 1M PHY via GATT command");
|
||||
} else if (requested_phy == 1) { /* 1 = PHY_2M */
|
||||
rc = ble_gap_set_prefered_le_phy(conn_handle, BLE_HCI_LE_PHY_2M_PREF_MASK, BLE_HCI_LE_PHY_2M_PREF_MASK, 0);
|
||||
ESP_LOGI(tag, "Central requested 2M PHY via GATT command");
|
||||
} else if (requested_phy == 2) { /* 2 = PHY_CODED_S2 */
|
||||
rc = ble_gap_set_prefered_le_phy(conn_handle, BLE_HCI_LE_PHY_CODED_PREF_MASK, BLE_HCI_LE_PHY_CODED_PREF_MASK, 0x01);
|
||||
ESP_LOGI(tag, "Central requested S2 PHY via GATT command");
|
||||
} else if (requested_phy == 3) { /* 3 = PHY_CODED_S8 */
|
||||
rc = ble_gap_set_prefered_le_phy(conn_handle, BLE_HCI_LE_PHY_CODED_PREF_MASK, BLE_HCI_LE_PHY_CODED_PREF_MASK, 0x02);
|
||||
ESP_LOGI(tag, "Central requested S8 PHY via GATT command");
|
||||
}
|
||||
|
||||
if (rc != 0) {
|
||||
ESP_LOGE(tag, "Failed to set preferred LE PHY; rc=%d", rc);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
rc = gatt_svr_chr_write(conn_handle, attr_handle,
|
||||
ctxt->om, 0,
|
||||
sizeof gatt_svr_thrpt_static_short_val,
|
||||
gatt_svr_thrpt_static_short_val, NULL);
|
||||
return rc;
|
||||
} else if (ctxt->op == BLE_GATT_ACCESS_OP_READ_CHR) {
|
||||
rc = os_mbuf_append(ctxt->om, gatt_svr_thrpt_static_short_val,
|
||||
sizeof gatt_svr_thrpt_static_short_val);
|
||||
return rc == 0 ? 0 : BLE_ATT_ERR_INSUFFICIENT_RES;
|
||||
}
|
||||
return BLE_ATT_ERR_UNLIKELY;
|
||||
|
||||
default:
|
||||
assert(0);
|
||||
return BLE_ATT_ERR_UNLIKELY;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
gatt_svr_register_cb(struct ble_gatt_register_ctxt *ctxt, void *arg)
|
||||
{
|
||||
char buf[BLE_UUID_STR_LEN];
|
||||
|
||||
switch (ctxt->op) {
|
||||
case BLE_GATT_REGISTER_OP_SVC:
|
||||
ESP_LOGD(tag, "registered service %s with handle=%d",
|
||||
ble_uuid_to_str(ctxt->svc.svc_def->uuid, buf),
|
||||
ctxt->svc.handle);
|
||||
break;
|
||||
|
||||
case BLE_GATT_REGISTER_OP_CHR:
|
||||
ESP_LOGD(tag, "registering characteristic %s with "
|
||||
"def_handle=%d val_handle=%d\n",
|
||||
ble_uuid_to_str(ctxt->chr.chr_def->uuid, buf),
|
||||
ctxt->chr.def_handle,
|
||||
ctxt->chr.val_handle);
|
||||
break;
|
||||
|
||||
case BLE_GATT_REGISTER_OP_DSC:
|
||||
ESP_LOGD(tag, "registering descriptor %s with handle=%d",
|
||||
ble_uuid_to_str(ctxt->dsc.dsc_def->uuid, buf),
|
||||
ctxt->dsc.handle);
|
||||
break;
|
||||
|
||||
default:
|
||||
assert(0);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
int
|
||||
gatt_svr_init(void)
|
||||
{
|
||||
int rc;
|
||||
|
||||
#if CONFIG_BT_NIMBLE_GAP_SERVICE
|
||||
ble_svc_gap_init();
|
||||
#endif /* CONFIG_BT_NIMBLE_GAP_SERVICE */
|
||||
#if MYNEWT_VAL(BLE_GATTS)
|
||||
ble_svc_gatt_init();
|
||||
#endif
|
||||
|
||||
rc = ble_gatts_count_cfg(gatts_test_svcs);
|
||||
if (rc != 0) {
|
||||
return rc;
|
||||
}
|
||||
|
||||
rc = ble_gatts_add_svcs(gatts_test_svcs);
|
||||
if (rc != 0) {
|
||||
return rc;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#ifndef H_GATTS_SENS_
|
||||
#define H_GATTS_SENS_
|
||||
|
||||
#include "nimble/ble.h"
|
||||
#include "modlog/modlog.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
extern uint16_t notify_handle;
|
||||
|
||||
struct ble_hs_cfg;
|
||||
struct ble_gatt_register_ctxt;
|
||||
|
||||
void gatt_svr_register_cb(struct ble_gatt_register_ctxt *ctxt, void *arg);
|
||||
int gatt_svr_init(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,548 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2015-2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include "esp_log.h"
|
||||
#include "nvs_flash.h"
|
||||
#include "freertos/FreeRTOSConfig.h"
|
||||
/* BLE */
|
||||
#include "nimble/nimble_port.h"
|
||||
#include "nimble/nimble_port_freertos.h"
|
||||
#include "host/ble_hs.h"
|
||||
#include "host/util/util.h"
|
||||
#include "console/console.h"
|
||||
#include "services/gap/ble_svc_gap.h"
|
||||
#include "gatts_sens.h"
|
||||
#include "../src/ble_hs_hci_priv.h"
|
||||
|
||||
#if CONFIG_EXAMPLE_EXTENDED_ADV
|
||||
static uint8_t ext_adv_pattern[] = {
|
||||
0x02, BLE_HS_ADV_TYPE_FLAGS, 0x06,
|
||||
0x03, BLE_HS_ADV_TYPE_COMP_UUIDS16, 0xab, 0xcd,
|
||||
0x03, BLE_HS_ADV_TYPE_COMP_UUIDS16, 0xAB, 0xF2,
|
||||
0x0c, BLE_HS_ADV_TYPE_COMP_NAME, 'n', 'i', 'm', 'b', 'l', 'e', '_', 'p', 'r', 'p', 'h'
|
||||
};
|
||||
|
||||
static uint8_t s_current_phy;
|
||||
#endif
|
||||
|
||||
static const char *device_name = "nimble_prph";
|
||||
|
||||
#define NOTIFY_THROUGHPUT_PAYLOAD 495 /* Exactly aligns with 2 LL packets */
|
||||
#define MIN_REQUIRED_MBUF 10 /* Reserve mbufs for incoming ATT packets */
|
||||
#define NOTIFY_PIPELINE_DEPTH 8 /* Number of notifications to keep in flight */
|
||||
#define PREFERRED_MTU_VALUE 512
|
||||
#define LL_PACKET_TIME 2120
|
||||
#define LL_PACKET_LENGTH 251
|
||||
#define MTU_DEF 512
|
||||
|
||||
static const char *tag = "bleprph_throughput";
|
||||
static TaskHandle_t notify_task_handle = NULL;
|
||||
static bool notify_state;
|
||||
static int notify_test_time = 60;
|
||||
static uint16_t conn_handle;
|
||||
/* Dummy variable */
|
||||
static uint8_t dummy;
|
||||
static uint8_t gatts_addr_type;
|
||||
|
||||
static int gatts_gap_event(struct ble_gap_event *event, void *arg);
|
||||
|
||||
#if CONFIG_EXAMPLE_EXTENDED_ADV
|
||||
void set_default_le_phy(uint8_t tx_phys_mask, uint8_t rx_phys_mask)
|
||||
{
|
||||
int rc = ble_gap_set_prefered_default_le_phy(tx_phys_mask, rx_phys_mask);
|
||||
if (rc == 0) {
|
||||
ESP_LOGI(tag, "Default LE PHY set successfully");
|
||||
} else {
|
||||
ESP_LOGE(tag, "Failed to set default LE PHY");
|
||||
}
|
||||
}
|
||||
|
||||
static struct os_mbuf *
|
||||
ext_get_data(uint8_t ext_adv_pattern[], int size)
|
||||
{
|
||||
struct os_mbuf *data;
|
||||
int rc;
|
||||
|
||||
data = os_msys_get_pkthdr(size, 0);
|
||||
assert(data);
|
||||
|
||||
rc = os_mbuf_append(data, ext_adv_pattern, size);
|
||||
assert(rc == 0);
|
||||
|
||||
return data;
|
||||
}
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Utility function to log an array of bytes.
|
||||
*/
|
||||
|
||||
void
|
||||
print_bytes(const uint8_t *bytes, int len)
|
||||
{
|
||||
int i;
|
||||
for (i = 0; i < len; i++) {
|
||||
ESP_LOGI(tag, "%s0x%02x", i != 0 ? ":" : "", bytes[i]);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
print_addr(const void *addr)
|
||||
{
|
||||
const uint8_t *u8p;
|
||||
|
||||
u8p = addr;
|
||||
ESP_LOGI(tag, "%02x:%02x:%02x:%02x:%02x:%02x",
|
||||
u8p[5], u8p[4], u8p[3], u8p[2], u8p[1], u8p[0]);
|
||||
}
|
||||
|
||||
static void
|
||||
bleprph_print_conn_desc(struct ble_gap_conn_desc *desc)
|
||||
{
|
||||
ESP_LOGI(tag, "handle=%d our_ota_addr_type=%d our_ota_addr=",
|
||||
desc->conn_handle, desc->our_ota_addr.type);
|
||||
print_addr(desc->our_ota_addr.val);
|
||||
ESP_LOGI(tag, " our_id_addr_type=%d our_id_addr=",
|
||||
desc->our_id_addr.type);
|
||||
print_addr(desc->our_id_addr.val);
|
||||
ESP_LOGI(tag, " peer_ota_addr_type=%d peer_ota_addr=",
|
||||
desc->peer_ota_addr.type);
|
||||
print_addr(desc->peer_ota_addr.val);
|
||||
ESP_LOGI(tag, " peer_id_addr_type=%d peer_id_addr=",
|
||||
desc->peer_id_addr.type);
|
||||
print_addr(desc->peer_id_addr.val);
|
||||
ESP_LOGI(tag, " conn_itvl=%d conn_latency=%d supervision_timeout=%d "
|
||||
"encrypted=%d authenticated=%d bonded=%d",
|
||||
desc->conn_itvl, desc->conn_latency,
|
||||
desc->supervision_timeout,
|
||||
desc->sec_state.encrypted,
|
||||
desc->sec_state.authenticated,
|
||||
desc->sec_state.bonded);
|
||||
}
|
||||
|
||||
#if CONFIG_EXAMPLE_EXTENDED_ADV
|
||||
/**
|
||||
* Enables advertising with the following parameters:
|
||||
* o General discoverable mode.
|
||||
* o Undirected connectable mode.
|
||||
*/
|
||||
static void
|
||||
ext_bleprph_advertise(void)
|
||||
{
|
||||
struct ble_gap_ext_adv_params params;
|
||||
struct os_mbuf *data = NULL;
|
||||
uint8_t instance = 0;
|
||||
int rc;
|
||||
|
||||
/* use defaults for non-set params */
|
||||
memset (¶ms, 0, sizeof(params));
|
||||
|
||||
params.scannable = 1;
|
||||
params.legacy_pdu = 1;
|
||||
|
||||
/*enable connectable advertising for all Phy*/
|
||||
params.connectable = 1;
|
||||
|
||||
/* advertise using random addr */
|
||||
params.own_addr_type = BLE_OWN_ADDR_PUBLIC;
|
||||
|
||||
/* Set current phy; get mbuf for scan rsp data; fill mbuf with scan rsp data */
|
||||
params.primary_phy = BLE_HCI_LE_PHY_1M_PREF_MASK ;
|
||||
params.secondary_phy = BLE_HCI_LE_PHY_2M_PREF_MASK ;
|
||||
data = ext_get_data(ext_adv_pattern, sizeof(ext_adv_pattern));
|
||||
params.sid = 0;
|
||||
|
||||
params.itvl_min = BLE_GAP_ADV_FAST_INTERVAL1_MIN;
|
||||
params.itvl_max = BLE_GAP_ADV_FAST_INTERVAL1_MIN;
|
||||
|
||||
/* configure instance 0 */
|
||||
rc = ble_gap_ext_adv_configure(instance, ¶ms, NULL,
|
||||
gatts_gap_event, NULL);
|
||||
assert (rc == 0);
|
||||
|
||||
rc = ble_gap_ext_adv_set_data(instance, data);
|
||||
assert (rc == 0);
|
||||
|
||||
/* start advertising */
|
||||
rc = ble_gap_ext_adv_start(instance, 0, 0);
|
||||
assert (rc == 0);
|
||||
}
|
||||
#else
|
||||
/*
|
||||
* Enables advertising with parameters:
|
||||
* o General discoverable mode
|
||||
* o Undirected connectable mode
|
||||
*/
|
||||
static void
|
||||
gatts_advertise(void)
|
||||
{
|
||||
struct ble_gap_adv_params adv_params;
|
||||
struct ble_hs_adv_fields fields;
|
||||
int rc;
|
||||
|
||||
/*
|
||||
* Set the advertisement data included in our advertisements:
|
||||
* o Flags (indicates advertisement type and other general info)
|
||||
* o Advertising tx power
|
||||
* o Device name
|
||||
*/
|
||||
memset(&fields, 0, sizeof(fields));
|
||||
|
||||
/*
|
||||
* Advertise two flags:
|
||||
* o Discoverability in forthcoming advertisement (general)
|
||||
* o BLE-only (BR/EDR unsupported)
|
||||
*/
|
||||
fields.flags = BLE_HS_ADV_F_DISC_GEN |
|
||||
BLE_HS_ADV_F_BREDR_UNSUP;
|
||||
|
||||
/*
|
||||
* Indicate that the TX power level field should be included; have the
|
||||
* stack fill this value automatically. This is done by assigning the
|
||||
* special value BLE_HS_ADV_TX_PWR_LVL_AUTO.
|
||||
*/
|
||||
fields.tx_pwr_lvl_is_present = 1;
|
||||
fields.tx_pwr_lvl = BLE_HS_ADV_TX_PWR_LVL_AUTO;
|
||||
fields.name = (uint8_t *)device_name;
|
||||
fields.name_len = strlen(device_name);
|
||||
fields.name_is_complete = 1;
|
||||
|
||||
rc = ble_gap_adv_set_fields(&fields);
|
||||
if (rc != 0) {
|
||||
ESP_LOGE(tag, "Error setting advertisement data; rc=%d", rc);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Begin advertising */
|
||||
memset(&adv_params, 0, sizeof(adv_params));
|
||||
adv_params.conn_mode = BLE_GAP_CONN_MODE_UND;
|
||||
adv_params.disc_mode = BLE_GAP_DISC_MODE_GEN;
|
||||
rc = ble_gap_adv_start(gatts_addr_type, NULL, BLE_HS_FOREVER,
|
||||
&adv_params, gatts_gap_event, NULL);
|
||||
if (rc != 0) {
|
||||
ESP_LOGE(tag, "Error enabling advertisement; rc=%d", rc);
|
||||
return;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
/* This function sends notifications to the client */
|
||||
static void
|
||||
notify_task(void *arg)
|
||||
{
|
||||
static uint8_t payload[NOTIFY_THROUGHPUT_PAYLOAD] = {0};/* Data payload */
|
||||
int rc, notify_count = 0;
|
||||
int64_t start_time, end_time, notify_time = 0;
|
||||
struct os_mbuf *om;
|
||||
|
||||
payload[0] = dummy; /* storing dummy data */
|
||||
payload[1] = rand();
|
||||
payload[99] = rand();
|
||||
|
||||
while (!notify_state) {
|
||||
vTaskDelay(1000 / portTICK_PERIOD_MS);
|
||||
}
|
||||
|
||||
while (1) {
|
||||
switch (notify_test_time) {
|
||||
|
||||
case 0:
|
||||
vTaskDelay(1000 / portTICK_PERIOD_MS);
|
||||
break;
|
||||
default:
|
||||
start_time = esp_timer_get_time();
|
||||
|
||||
if (!notify_state) {
|
||||
vTaskDelay(1000 / portTICK_PERIOD_MS);
|
||||
break;
|
||||
}
|
||||
|
||||
while (notify_state && notify_time < (notify_test_time * 1000)) {
|
||||
ulTaskNotifyTake(pdFALSE, portMAX_DELAY);
|
||||
|
||||
/* Stop immediately if central unsubscribed */
|
||||
if (!notify_state) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (dummy == 200) {
|
||||
dummy = 0;
|
||||
}
|
||||
dummy++;
|
||||
|
||||
/* Check if the MBUFs are available */
|
||||
if (os_msys_num_free() >= MIN_REQUIRED_MBUF) {
|
||||
do {
|
||||
om = ble_hs_mbuf_from_flat(payload, sizeof(payload));
|
||||
if (om == NULL) {
|
||||
/* Memory not available for mbuf, yield briefly */
|
||||
vTaskDelay(1);
|
||||
}
|
||||
} while (om == NULL);
|
||||
|
||||
rc = ble_gatts_notify_custom(conn_handle, notify_handle, om);
|
||||
if (rc != 0) {
|
||||
if (rc == BLE_HS_ENOMEM) {
|
||||
ESP_LOGD(tag, "Notification queue full (expected backpressure)");
|
||||
} else {
|
||||
ESP_LOGE(tag, "Error while sending notification; rc = %d", rc);
|
||||
}
|
||||
notify_count -= 1;
|
||||
/* Yield to let mbufs free up */
|
||||
vTaskDelay(1);
|
||||
}
|
||||
} else {
|
||||
if (notify_task_handle) {
|
||||
xTaskNotifyGive(notify_task_handle);
|
||||
}
|
||||
notify_count -= 1;
|
||||
/* Yield briefly to let mbufs free up */
|
||||
vTaskDelay(1);
|
||||
}
|
||||
|
||||
end_time = esp_timer_get_time();
|
||||
notify_time = (end_time - start_time) / 1000 ;
|
||||
notify_count += 1;
|
||||
}
|
||||
|
||||
/* Use actual elapsed time for accurate throughput calculation */
|
||||
{
|
||||
int actual_secs = (int)(notify_time / 1000);
|
||||
if (actual_secs > 0 && notify_count > 0) {
|
||||
printf("\n*********************************\n");
|
||||
ESP_LOGI(tag, "Notify throughput = %d bps, count = %d",
|
||||
(int)(((uint64_t)notify_count * NOTIFY_THROUGHPUT_PAYLOAD * 8) / actual_secs), notify_count);
|
||||
printf("\n*********************************\n");
|
||||
ESP_LOGI(tag, " Notification test complete (%d sec elapsed)", actual_secs);
|
||||
}
|
||||
}
|
||||
notify_test_time = 0;
|
||||
notify_count = 0;
|
||||
notify_time = 0;
|
||||
|
||||
break;
|
||||
}
|
||||
vTaskDelay(3000 / portTICK_PERIOD_MS);
|
||||
}
|
||||
}
|
||||
|
||||
static int
|
||||
gatts_gap_event(struct ble_gap_event *event, void *arg)
|
||||
{
|
||||
struct ble_gap_conn_desc desc;
|
||||
int rc;
|
||||
|
||||
switch (event->type) {
|
||||
case BLE_GAP_EVENT_CONNECT:
|
||||
/* A new connection was established or a connection attempt failed */
|
||||
ESP_LOGI(tag, "connection %s; status = %d ",
|
||||
event->connect.status == 0 ? "established" : "failed",
|
||||
event->connect.status);
|
||||
rc = ble_att_set_preferred_mtu(PREFERRED_MTU_VALUE);
|
||||
if (rc != 0) {
|
||||
ESP_LOGE(tag, "Failed to set preferred MTU; rc = %d", rc);
|
||||
}
|
||||
|
||||
if (event->connect.status != 0) {
|
||||
/* Connection failed; resume advertising */
|
||||
#if CONFIG_EXAMPLE_EXTENDED_ADV
|
||||
ext_bleprph_advertise();
|
||||
#else
|
||||
gatts_advertise();
|
||||
#endif
|
||||
}
|
||||
|
||||
rc = ble_hs_hci_util_set_data_len(event->connect.conn_handle,
|
||||
LL_PACKET_LENGTH,
|
||||
LL_PACKET_TIME);
|
||||
if (rc != 0) {
|
||||
ESP_LOGE(tag, "Set packet length failed");
|
||||
}
|
||||
|
||||
conn_handle = event->connect.conn_handle;
|
||||
break;
|
||||
|
||||
case BLE_GAP_EVENT_DISCONNECT:
|
||||
ESP_LOGI(tag, "disconnect; reason = %d", event->disconnect.reason);
|
||||
|
||||
/* Stop notification task loop cleanly */
|
||||
notify_state = false;
|
||||
if (notify_task_handle) {
|
||||
xTaskNotifyGive(notify_task_handle);
|
||||
}
|
||||
|
||||
/* Connection terminated; resume advertising */
|
||||
#if CONFIG_EXAMPLE_EXTENDED_ADV
|
||||
ble_gap_ext_adv_stop(0);
|
||||
ext_bleprph_advertise();
|
||||
#else
|
||||
gatts_advertise();
|
||||
#endif
|
||||
break;
|
||||
|
||||
case BLE_GAP_EVENT_CONN_UPDATE:
|
||||
/* The central has updated the connection parameters. */
|
||||
ESP_LOGI(tag, "connection updated; status=%d ",
|
||||
event->conn_update.status);
|
||||
rc = ble_gap_conn_find(event->conn_update.conn_handle, &desc);
|
||||
assert(rc == 0);
|
||||
bleprph_print_conn_desc(&desc);
|
||||
return 0;
|
||||
|
||||
case BLE_GAP_EVENT_ADV_COMPLETE:
|
||||
ESP_LOGI(tag, "adv complete ");
|
||||
break;
|
||||
|
||||
case BLE_GAP_EVENT_SUBSCRIBE:
|
||||
ESP_LOGI(tag, "subscribe event; cur_notify=%d; value handle; "
|
||||
"val_handle = %d",
|
||||
event->subscribe.cur_notify, event->subscribe.attr_handle);
|
||||
if (event->subscribe.attr_handle == notify_handle) {
|
||||
notify_state = event->subscribe.cur_notify;
|
||||
if (notify_state) {
|
||||
/* Always reset test time on new subscription.
|
||||
* The central controls the actual duration by unsubscribing.
|
||||
* Use a large default so the peripheral never stops on its own. */
|
||||
notify_test_time = 3600;
|
||||
ESP_LOGI(tag, "Notifications enabled, test time = %d sec", notify_test_time);
|
||||
/* Prime the notification pipeline to allow multiple in-flight
|
||||
* notifications. This enables the controller to fill connection
|
||||
* events with back-to-back PDUs for maximum throughput. */
|
||||
for (int i = 0; i < NOTIFY_PIPELINE_DEPTH; i++) {
|
||||
if (notify_task_handle) {
|
||||
xTaskNotifyGive(notify_task_handle);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ESP_LOGI(tag, "Notifications disabled");
|
||||
}
|
||||
} else if (event->subscribe.attr_handle != notify_handle) {
|
||||
notify_state = event->subscribe.cur_notify;
|
||||
}
|
||||
break;
|
||||
|
||||
case BLE_GAP_EVENT_NOTIFY_TX:
|
||||
ESP_LOGD(tag, "BLE_GAP_EVENT_NOTIFY_TX success !!");
|
||||
|
||||
/* Always return the pipeline token to prevent the task from blocking permanently,
|
||||
* even if the notification failed (e.g., due to disconnect or buffer full). */
|
||||
if (notify_task_handle) {
|
||||
xTaskNotifyGive(notify_task_handle);
|
||||
}
|
||||
|
||||
if ((event->notify_tx.status != 0) &&
|
||||
(event->notify_tx.status != BLE_HS_EDONE)) {
|
||||
if (event->notify_tx.status == BLE_HS_ENOMEM) {
|
||||
ESP_LOGD(tag, "BLE_GAP_EVENT_NOTIFY_TX flow control (ENOMEM)");
|
||||
} else {
|
||||
ESP_LOGE(tag, "BLE_GAP_EVENT_NOTIFY_TX notify tx status = %d", event->notify_tx.status);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case BLE_GAP_EVENT_MTU:
|
||||
ESP_LOGI(tag, "mtu update event; conn_handle = %d mtu = %d ",
|
||||
event->mtu.conn_handle,
|
||||
event->mtu.value);
|
||||
break;
|
||||
|
||||
#if CONFIG_EXAMPLE_EXTENDED_ADV
|
||||
case BLE_GAP_EVENT_PHY_UPDATE_COMPLETE:
|
||||
ESP_LOGI(tag, "PHY Update Event: Status=%d, Conn_Handle=0x%04X, TX_PHY=%d, RX_PHY=%d",
|
||||
event->phy_updated.status, event->phy_updated.conn_handle,
|
||||
event->phy_updated.tx_phy, event->phy_updated.rx_phy);
|
||||
|
||||
#endif
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void
|
||||
gatts_on_sync(void)
|
||||
{
|
||||
int rc;
|
||||
#if CONFIG_EXAMPLE_EXTENDED_ADV
|
||||
uint8_t all_phy;
|
||||
#endif
|
||||
uint8_t addr_val[6] = {0};
|
||||
|
||||
rc = ble_hs_id_infer_auto(0, &gatts_addr_type);
|
||||
assert(rc == 0);
|
||||
rc = ble_hs_id_copy_addr(gatts_addr_type, addr_val, NULL);
|
||||
assert(rc == 0);
|
||||
ESP_LOGI(tag, "Device Address: ");
|
||||
print_addr(addr_val);
|
||||
/* Begin advertising */
|
||||
#if CONFIG_EXAMPLE_EXTENDED_ADV
|
||||
s_current_phy = BLE_HCI_LE_PHY_1M_PREF_MASK | BLE_HCI_LE_PHY_2M_PREF_MASK | BLE_HCI_LE_PHY_CODED_PREF_MASK;;
|
||||
|
||||
all_phy = BLE_HCI_LE_PHY_1M_PREF_MASK | BLE_HCI_LE_PHY_2M_PREF_MASK | BLE_HCI_LE_PHY_CODED_PREF_MASK;
|
||||
|
||||
set_default_le_phy(all_phy, all_phy);
|
||||
|
||||
ext_bleprph_advertise();
|
||||
#else
|
||||
gatts_advertise();
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
static void
|
||||
gatts_on_reset(int reason)
|
||||
{
|
||||
ESP_LOGE(tag, "Resetting state; reason=%d", reason);
|
||||
}
|
||||
|
||||
void gatts_host_task(void *param)
|
||||
{
|
||||
ESP_LOGI(tag, "BLE Host Task Started");
|
||||
/* This function will return only when nimble_port_stop() is executed */
|
||||
nimble_port_run();
|
||||
nimble_port_freertos_deinit();
|
||||
}
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
int rc;
|
||||
|
||||
/* Initialize NVS — it is used to store PHY calibration data */
|
||||
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);
|
||||
|
||||
ret = nimble_port_init();
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(tag, "Failed to init nimble %d ", ret);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Initialize the NimBLE host configuration */
|
||||
ble_hs_cfg.sync_cb = gatts_on_sync;
|
||||
ble_hs_cfg.reset_cb = gatts_on_reset;
|
||||
ble_hs_cfg.gatts_register_cb = gatt_svr_register_cb,
|
||||
ble_hs_cfg.store_status_cb = ble_store_util_status_rr;
|
||||
|
||||
/* Initialize Notify Task */
|
||||
BaseType_t task_rc = xTaskCreate(notify_task, "notify_task", 4096, NULL, 10, ¬ify_task_handle);
|
||||
if (task_rc != pdPASS) {
|
||||
ESP_LOGE(tag, "Failed to create notify_task (rc=%d)", task_rc);
|
||||
return ;
|
||||
}
|
||||
#if MYNEWT_VAL(BLE_GATTS)
|
||||
rc = gatt_svr_init();
|
||||
assert(rc == 0);
|
||||
|
||||
/* Set the default device name */
|
||||
rc = ble_svc_gap_device_name_set(device_name);
|
||||
assert(rc == 0);
|
||||
#endif
|
||||
|
||||
/* Start the task */
|
||||
nimble_port_freertos_init(gatts_host_task);
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
# Override some defaults so BT stack is enabled
|
||||
# in this example and some example specific misc sizes are increased.
|
||||
|
||||
#
|
||||
# BT config (universal across all ESP32 variants)
|
||||
#
|
||||
CONFIG_BT_ENABLED=y
|
||||
CONFIG_BT_BLUEDROID_ENABLED=n
|
||||
CONFIG_BT_NIMBLE_ENABLED=y
|
||||
|
||||
# === BT 5.0 / 2M PHY support ===
|
||||
# Ignored automatically on classic ESP32 which lacks BT 5.0
|
||||
CONFIG_BT_NIMBLE_EXT_ADV=n
|
||||
CONFIG_BT_NIMBLE_50_FEATURE_SUPPORT=y
|
||||
CONFIG_EXAMPLE_EXTENDED_ADV=n
|
||||
|
||||
# === ATT / GATT ===
|
||||
CONFIG_BT_NIMBLE_ATT_PREFERRED_MTU=512
|
||||
|
||||
# === Memory Pool Tuning ===
|
||||
CONFIG_BT_NIMBLE_MAX_CONNECTIONS=1
|
||||
CONFIG_BT_NIMBLE_MSYS_1_BLOCK_COUNT=80
|
||||
CONFIG_BT_NIMBLE_MSYS_2_BLOCK_SIZE=592
|
||||
CONFIG_BT_NIMBLE_MSYS_2_BLOCK_COUNT=80
|
||||
|
||||
# Increase controller-to-host buffers
|
||||
CONFIG_BT_NIMBLE_TRANSPORT_ACL_FROM_LL_COUNT=40
|
||||
CONFIG_BT_NIMBLE_TRANSPORT_ACL_SIZE=255
|
||||
CONFIG_BT_NIMBLE_TRANSPORT_EVT_SIZE=255
|
||||
|
||||
# Disable NimBLE INFO logging for throughput tests
|
||||
CONFIG_BT_NIMBLE_LOG_LEVEL_WARNING=y
|
||||
CONFIG_BT_NIMBLE_LOG_LEVEL=2
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
# === High-Performance Tuning ===
|
||||
# This config is only applied when building for the ESP32-C6 target
|
||||
# It is necessary for the C6 to reach 750+ kbps and 1.3+ Mbps throughput.
|
||||
CONFIG_COMPILER_OPTIMIZATION_PERF=y
|
||||
CONFIG_FREERTOS_HZ=1000
|
||||
Reference in New Issue
Block a user