NimBLE: Add throughput demo examples

- Added blecent_throughput(client) and bleprph_throughput(server) examples to
  demonstrate application throughput.
This commit is contained in:
Prasad Alatkar 2020-06-03 23:59:59 +05:30 committed by isha pardikar
parent 02237e4b77
commit ed43a4eb50
26 changed files with 3554 additions and 0 deletions

View File

@ -0,0 +1,16 @@
# Throughput demo Examples
There are two different example folders inside this `throughput_app`, `bleprph_throughput` and `blecent_throughput`. As the names suggest, both of them play role of `peripheral` and `central` respectively. These example demonstrate application throughput for NimBLE on ESP32. Two ESP32 boards are needed to run this demo. The `blecent_throughput` example has CLI support to select GATT operation from READ/WRITE/NOTIFY. It can also accept connection parameters at start of program, more details can be found in respective READMEs.
## Using the examples
First build and flash two ESP32 boards with `bleprph_throughput` and `blecent_throughput` examples. The central automatically scans and connects to peripheral based on peripheral name string (`nimble_prph`). In the next step, user may choose to configure connection parameters (`MTU`, `connection interval`, `latency`, `supervision timeout`, `connection event length`). In the next step, user needs to specify throughput test name (`read`, `write` or `notify`) and test time in seconds. Below are
sample numbers of different throughput test runs for 60 seconds (MTU = 512, conn itvl = 7.5msec, conn event length = 7.5msec)
|GATT method | Measurement time | Application Throughput|
|--- | --- | ---|
|NOTIFY | 60 seconds | ~530Kbps|
|READ | 60 seconds | ~180kbps|
|WRITE | 60 seconds | ~180kbps|
The notify output is seen on `bleprph_throughput` console and read/write throughput are seen on `blecent_throughput` console.

View File

@ -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.5)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(blecent_throughput)

View File

@ -0,0 +1,8 @@
#
# This is a project Makefile. It is assumed the directory this Makefile resides in is a
# project subdirectory.
#
PROJECT_NAME := blecent_throughput
include $(IDF_PATH)/make/project.mk

View File

@ -0,0 +1,147 @@
# 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 operation on peripheral's `THRPT_LONG_CHR_READ_WRITE` characteristic, write operation on `THRPT_CHR_READ_WRITE` and subscribes to `THRPT_CHR_NOTIFY` characteristic for notifications. If user does not specify any throughput test method for 30 seconds (`BLE_RX_TIMEOUT`) then the program starts with default 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 parameters).
* `bleprph_throughput` example needs to be run along with this client side example.
* After connection link is established between these two devices, user is given a window of `YES_NO_PARAM` (5 seconds) to customize connection parameters. If user has configured parameters from menuconfig, this step can be skipped by either waiting it out or entering `Insert no`.
* User needs to 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 120 0 500 0 0` for connection parameters in sequence of `min conn_itvl`, `max conn_itvl`, `latency`, `supervision timeout`, `min conn_evt_len` and `max_conn_evt_len`.
* User will be now presented with throughput test related console prints, this suggests application is now ready to be run throughput test for user defined time. The prints may appear like below
```
==================================================================
| Steps to test nimble throughput |
| |
| 1. Enter throughput [--Type] [--Test time] |
| Type: read/write/notify. |
| Test time: Enter value in seconds. |
| |
| e.g. throughput read 600 |
| |
| ** Enter 'throughput read 60' for reading char for 60 seconds |
| OR 'throughput write 60' for writing to char for 60 seconds |
| OR 'throughput notify 60' for notifications (for 60 seconds)**|
| |
=================================================================
```
* If user fail to enter any values for next 30 seconds, the app falls to default behavior of READ for 60 seconds mode.
* Read and write throughput numbers will be presented in `blecent_throughput` console output. For notification `bleprph_throughput` console shall be referred, as the peripheral is the one who is sending notifications. Below is the sample output of the app:
```
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
ל<EFBFBD>j6<EFBFBD>^n<><6E><EFBFBD><EFBFBD>
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] |
| Type: read/write/notify. |
| Test time: Enter value in seconds. |
| |
| e.g. throughput read 600 |
| |
| ** Enter 'throughput read 60' for reading char for 60 seconds |
| OR 'throughput write 60' for writing to char for 60 seconds |
| OR 'throughput notify 60' for notifications (for 60 seconds)**|
| |
=================================================================
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 tries to demonstrate stable implementation of GATT operations like read/write and notify. The READ and WRITE GATT operations require ACK from peer, so this central app makes sure that next GATT operation is performed after completion of previous one. In case of notification (`bleprph_throughput` sends) operation there is no waiting for ACK, however one needs to mind `os_mbufs` getting full, so one may need to allocate higher number of mbufs through menuconfig.
## 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
****************************************************************
```

View File

@ -0,0 +1,3 @@
idf_component_register(SRCS "cmd_system.c"
INCLUDE_DIRS "."
REQUIRES console spi_flash)

View File

@ -0,0 +1,339 @@
/* Console example — various system commands
This example code is in the Public Domain (or CC0 licensed, at your option.)
Unless required by applicable law or agreed to in writing, this
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied.
*/
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include "esp_log.h"
#include "esp_console.h"
#include "esp_system.h"
#include "esp_sleep.h"
#include "esp_spi_flash.h"
#include "driver/rtc_io.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);
static void register_deep_sleep(void);
static void register_light_sleep(void);
#if WITH_TASKS_INFO
static void register_tasks(void);
#endif
void register_system(void)
{
register_free();
register_heap();
register_version();
register_restart();
register_deep_sleep();
register_light_sleep();
#if WITH_TASKS_INFO
register_tasks();
#endif
}
/* 'version' command */
static int get_version(int argc, char **argv)
{
esp_chip_info_t info;
esp_chip_info(&info);
printf("IDF Version:%s\r\n", esp_get_idf_version());
printf("Chip info:\r\n");
printf("\tmodel:%s\r\n", info.model == CHIP_ESP32 ? "ESP32" : "Unknow");
printf("\tcores:%d\r\n", info.cores);
printf("\tfeature:%s%s%s%s%d%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:",
spi_flash_get_chip_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("%d\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 minumum 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: %u", 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
/** '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");
ESP_ERROR_CHECK( esp_sleep_enable_ext1_wakeup(1ULL << io_num, level) );
}
rtc_gpio_isolate(GPIO_NUM_12);
esp_deep_sleep_start();
}
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) );
}
/** '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();
esp_sleep_wakeup_cause_t cause = esp_sleep_get_wakeup_cause();
const char *cause_str;
switch (cause) {
case ESP_SLEEP_WAKEUP_GPIO:
cause_str = "GPIO";
break;
case ESP_SLEEP_WAKEUP_UART:
cause_str = "UART";
break;
case ESP_SLEEP_WAKEUP_TIMER:
cause_str = "timer";
break;
default:
cause_str = "unknown";
printf("%d\n", cause);
}
ESP_LOGI(TAG, "Woke up from: %s", cause_str);
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) );
}

View File

@ -0,0 +1,20 @@
/* Console example — various system commands
This example code is in the Public Domain (or CC0 licensed, at your option.)
Unless required by applicable law or agreed to in writing, this
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied.
*/
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
// Register system functions
void register_system(void);
#ifdef __cplusplus
}
#endif

View File

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

View File

@ -0,0 +1,2 @@
idf_component_register(SRCS "main.c" "misc.c" "peer.c" "scli.c"
INCLUDE_DIRS ".")

View File

@ -0,0 +1,44 @@
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.
config EXAMPLE_CONN_ITVL_MAX
int "Maximum connection itvl"
default 6
help
Set the maximum connection interval in 1.25msec units.
config EXAMPLE_CONN_LATENCY
int "Connection latency"
default 0
help
Set the connection latency.
config EXAMPLE_CONN_TIMEOUT
int "Supervision timeout"
default 500
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.
config EXAMPLE_CONN_CE_LEN_MAX
int "Minimum connection itvl"
default 12
help
Set the maximum connection event length in 0.625msec units.
endmenu

View File

@ -0,0 +1,4 @@
#
# "main" pseudo-component makefile.
#
# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.)

View File

@ -0,0 +1,122 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#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
/** Misc. */
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);
struct peer_svc {
SLIST_ENTRY(peer_svc) next;
struct ble_gatt_svc svc;
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);
int peer_init(int max_peers, int max_svcs, int max_chrs, int max_dscs);
struct peer *
peer_find(uint16_t conn_handle);
/* Console */
int scli_init(void);
void ble_register_cli(void);
int scli_receive_key(int *key);
int cli_receive_key(int *key);
int scli_receive_yesno(bool *key);
void scli_reset_queue(void);
#ifdef __cplusplus
}
#endif
#endif

View File

@ -0,0 +1,827 @@
/*
* Copyright 2020 Espressif Systems (Shanghai) PTE LTD
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#include "esp_log.h"
#include "nvs_flash.h"
/* BLE */
#include "esp_nimble_hci.h"
#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 "gattc.h"
#include "esp_timer.h"
#include "linenoise/linenoise.h"
#include "esp_console.h"
#include "driver/uart.h"
#include "esp_vfs_dev.h"
#include "argtable3/argtable3.h"
#include "cmd_system.h"
#include "../src/ble_hs_hci_priv.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 THRPT_LONG_CHR_READ_WRITE_ALT 0x001a
#define THRPT_CHR_READ_WRITE_ALT 0x001f
/* Throughput cases */
#define READ_THROUGHPUT 1
#define WRITE_THROUGHPUT 2
#define NOTIFY_THROUGHPUT 3
#define READ_THROUGHPUT_PAYLOAD 500
#define WRITE_THROUGHPUT_PAYLOAD 500
#define LL_PACKET_TIME 2120
#define LL_PACKET_LENGTH 251
static const char *tag = "blecent_throughput";
static int blecent_gap_event(struct ble_gap_event *event, void *arg);
static uint8_t peer_addr[6];
static SemaphoreHandle_t xSemaphore;
static int mbuf_len_total;
static int failure_count;
static int conn_params_def[] = {40, 40, 0, 500, 80, 80};
static struct ble_gap_upd_params conn_params = {
/** Minimum value for connection interval in 1.25ms units */
.itvl_min = CONFIG_EXAMPLE_CONN_ITVL_MIN,
/** Maximum value for connection interval in 1.25ms units */
.itvl_max = CONFIG_EXAMPLE_CONN_ITVL_MAX,
/** Connection latency */
.latency = CONFIG_EXAMPLE_CONN_LATENCY,
/** Supervision timeout in 10ms units */
.supervision_timeout = CONFIG_EXAMPLE_CONN_TIMEOUT,
/** Minimum length of connection event in 0.625ms units */
.min_ce_len = CONFIG_EXAMPLE_CONN_CE_LEN_MIN,
/** Maximum length of connection event in 0.625ms units */
.max_ce_len = CONFIG_EXAMPLE_CONN_CE_LEN_MAX,
};
static int mtu_def = 512;
/* test_data accepts test_name and test_time from CLI */
static int test_data[] = {1, 600};
void ble_store_config_init(void);
static int
blecent_notify(uint16_t conn_handle, uint16_t val_handle,
ble_gatt_attr_fn *cb, struct peer *peer, int test_time)
{
uint8_t value[2] = {1, 0};/*To subscribe to notifications*/
int rc;
rc = ble_gattc_write_flat(conn_handle, val_handle,
value, sizeof value, NULL, &test_time);
if (rc != 0) {
ESP_LOGE(tag, "Error: Failed to subscribe to characteristic; "
"rc = %d", rc);
goto err;
}
return 0;
err:
/* Terminate the connection. */
return ble_gap_terminate(peer->conn_handle, BLE_ERR_REM_USER_CONN_TERM);
}
static int
blecent_repeat_write(uint16_t conn_handle,
const struct ble_gatt_error *error,
struct ble_gatt_attr *attr,
void *arg)
{
if (error->status == 0) {
xSemaphoreGive(xSemaphore);
ESP_LOGD(tag, " attr_handle=%d value=", attr->handle);
MODLOG_DFLT(INFO, "\n");
} else {
failure_count++;
xSemaphoreGive(xSemaphore);
ESP_LOGE(tag, " Error writing error code = %d", error->status);
}
return error->status;
}
static int blecent_write(uint16_t conn_handle, uint16_t val_handle,
ble_gatt_attr_fn *cb, struct peer *peer, int test_time)
{
int64_t start_time, end_time, write_time = 0;
int write_count = 0;
uint8_t value[WRITE_THROUGHPUT_PAYLOAD] = {0};
int rc;
value[0] = rand();
failure_count = 0;
start_time = esp_timer_get_time();
while (write_time < test_time * 1000) {
/* Wait till the previous write is complete. For first time Semaphore
* is already available */
xSemaphoreTake(xSemaphore, portMAX_DELAY);
rc = ble_gattc_write_flat(conn_handle, val_handle,
&value, sizeof value, blecent_repeat_write, NULL);
if (rc != 0) {
ESP_LOGE(tag, "Error: Failed to write characteristic; rc=%d\n",
rc);
goto err;
}
end_time = esp_timer_get_time();
write_time = (end_time - start_time) / 1000 ;
write_count += 1;
}
/* Each successful write is of WRITE_THROUGHPUT_PAYLOAD Bytes of
* application data. */
printf("\n****************************************************************\n");
ESP_LOGI(tag, "Application Write throughput = %d bps, write count = %d,"
"failure count = %d",
((write_count - failure_count) * 8 * WRITE_THROUGHPUT_PAYLOAD) / test_time,
write_count, failure_count);
printf("\n****************************************************************\n");
return 0;
err:
/* Terminate the connection. */
return ble_gap_terminate(peer->conn_handle, BLE_ERR_REM_USER_CONN_TERM);
}
static int
blecent_repeat_read(uint16_t conn_handle,
const struct ble_gatt_error *error,
struct ble_gatt_attr *attr,
void *arg)
{
if (error->status == 0) {
xSemaphoreGive(xSemaphore);
ESP_LOGD(tag, " attr_handle=%d value=", attr->handle);
mbuf_len_total += OS_MBUF_PKTLEN(attr->om);
} else {
ESP_LOGE(tag, " Read failed, callback error code = %d", error->status );
xSemaphoreGive(xSemaphore);
}
return error->status;
}
static int blecent_read(uint16_t conn_handle, uint16_t val_handle,
ble_gatt_attr_fn *cb, struct peer *peer, int test_time)
{
int rc, read_count = 0;
int64_t start_time, end_time, read_time = 0;
/* Keep track of number of bytes read from char */
mbuf_len_total = 0;
start_time = esp_timer_get_time();
ESP_LOGD(tag, " Throughput read started :val_handle=%d test_time=%d", val_handle,
test_time);
while (read_time < (test_time * 1000)) {
/* Wait till the previous read is complete. For first time use Semaphore
* is already available */
xSemaphoreTake(xSemaphore, portMAX_DELAY);
rc = ble_gattc_read(peer->conn_handle, val_handle,
blecent_repeat_read, (void *) &peer);
if (rc != 0) {
ESP_LOGE(tag, "Error: Failed to read characteristic; rc=%d",
rc);
goto err;
}
end_time = esp_timer_get_time();
read_time = (end_time - start_time) / 1000 ;
read_count += 1;
}
/* Application data throughput */
printf("\n****************************************************************\n");
ESP_LOGI(tag, "Application Read throughput = %d bps, Read op counter = %d",
(mbuf_len_total * 8) / (test_time), read_count);
printf("\n****************************************************************\n");
return 0;
err:
/* Terminate the connection. */
vTaskDelay(100 / portTICK_PERIOD_MS);
return ble_gap_terminate(peer->conn_handle, BLE_ERR_REM_USER_CONN_TERM);
}
static void throughput_task(void *arg)
{
struct peer *peer = (struct peer *)arg;
const struct peer_chr *chr;
const struct peer_dsc *dsc;
int rc = 0;
while (1) {
vTaskDelay(4000 / portTICK_PERIOD_MS);
ESP_LOGI(tag, "Format for throughput demo:: throughput read 100");
printf(" ==================================================================\n");
printf(" | Steps to test nimble throughput |\n");
printf(" | |\n");
printf(" | 1. Enter throughput [--Type] [--Test time] |\n");
printf(" | Type: read/write/notify. |\n");
printf(" | Test time: Enter value in seconds. |\n");
printf(" | |\n");
printf(" | e.g. throughput read 600 |\n");
printf(" | |\n");
printf(" | ** Enter 'throughput read 60' for reading char for 60 seconds |\n");
printf(" | OR 'throughput write 60' for writing to char for 60 seconds |\n");
printf(" | OR 'throughput notify 60' for notifications (for 60 seconds)**|\n");
printf(" | |\n");
printf(" =================================================================\n\n");
/* XXX Delay ? */
vTaskDelay(1000 / portTICK_PERIOD_MS);
if (!cli_receive_key(test_data)) {
/* No command supplied, start with reading */
test_data[0] = READ_THROUGHPUT;
test_data[1] = 60;
ESP_LOGI(tag, "No command received from user, start with READ op"
" with 60 seconds test time");
}
scli_reset_queue();
switch (test_data[0]) {
case READ_THROUGHPUT:
/* Read the characteristic supporting long read support
* `THRPT_LONG_CHR_READ_WRITE` (0x000b) */
chr = peer_chr_find_uuid(peer,
THRPT_UUID_DECLARE(THRPT_SVC),
THRPT_UUID_DECLARE(THRPT_LONG_CHR_READ_WRITE));
if (chr == NULL) {
ESP_LOGE(tag, "Peer does not support "
"LONG_READ (0x000b) characteristic ");
break;
}
if (test_data[1] > 0) {
rc = blecent_read(peer->conn_handle, chr->chr.val_handle,
blecent_repeat_read, (void *) peer, test_data[1]);
if (rc != 0) {
ESP_LOGE(tag, "Error while reading from GATTS; rc = %d", rc);
}
} else {
ESP_LOGE(tag, "Please enter non-zero value for test time in seconds!!");
}
break;
case WRITE_THROUGHPUT:
chr = peer_chr_find_uuid(peer,
THRPT_UUID_DECLARE(THRPT_SVC),
THRPT_UUID_DECLARE(THRPT_CHR_READ_WRITE));
if (chr == NULL) {
ESP_LOGE(tag, "Error: Peer doesn't support the READ "
"WRITE characteristic (0x0006) ");
break;
}
if (test_data[1] > 0) {
rc = blecent_write(peer->conn_handle, chr->chr.val_handle,
blecent_repeat_write, (void *) peer, test_data[1]);
if (rc != 0) {
ESP_LOGE(tag, "Error while writing data; rc = %d", rc);
}
} else {
ESP_LOGE(tag, "Please enter non-zero value for test time in seconds!!");
}
break;
case NOTIFY_THROUGHPUT:
chr = peer_chr_find_uuid(peer,
THRPT_UUID_DECLARE(THRPT_SVC),
THRPT_UUID_DECLARE(THRPT_CHR_NOTIFY));
if (chr == NULL) {
ESP_LOGE(tag, "Error: Peer doesn't support the NOTIFY "
"characteristic (0x000a) ");
break;
}
dsc = peer_dsc_find_uuid(peer,
THRPT_UUID_DECLARE(THRPT_SVC),
THRPT_UUID_DECLARE(THRPT_CHR_NOTIFY),
BLE_UUID16_DECLARE(BLE_GATT_DSC_CLT_CFG_UUID16));
if (dsc == NULL) {
ESP_LOGE(tag, "Error: Peer lacks a CCCD for the Notify "
"Status characteristic\n");
break;
}
rc = blecent_notify(peer->conn_handle, dsc->dsc.handle,
NULL, (void *) peer, test_data[1]);
if (rc != 0) {
ESP_LOGE(tag, "Subscribing to notification failed; rc = %d ", rc);
} else {
ESP_LOGI(tag, "Subscribed to notifications. Throughput number"
" can be seen on peripheral terminal after %d seconds",
test_data[1]);
}
vTaskDelay(test_data[1]*1000 / portTICK_PERIOD_MS);
break;
default:
break;
}
vTaskDelay(5000 / portTICK_PERIOD_MS);
}
vTaskDelete(NULL);
}
static void
blecent_read_write_subscribe(const struct peer *peer)
{
xTaskCreate(throughput_task, "throughput_task", 4096, (void *) peer, 10, NULL);
return;
}
/**
* Called when service discovery of the specified peer has completed.
*/
static void
blecent_on_disc_complete(const struct peer *peer, int status, void *arg)
{
if (status != 0) {
/* Service discovery failed. Terminate the connection. */
ESP_LOGE(tag, "Error: Service discovery failed; status=%d "
"conn_handle=%d\n", status, peer->conn_handle);
ble_gap_terminate(peer->conn_handle, BLE_ERR_REM_USER_CONN_TERM);
return;
}
/* Service discovery has completed successfully. Now we have a complete
* list of services, characteristics, and descriptors that the peer
* supports.
*/
ESP_LOGI(tag, "Service discovery complete; status=%d "
"conn_handle=%d\n", status, peer->conn_handle);
/* Now perform three GATT procedures against the peer: read,
* write, and subscribe to notifications depending upon user input.
*/
blecent_read_write_subscribe(peer);
}
/**
* Initiates the GAP general discovery procedure.
*/
static void
blecent_scan(void)
{
uint8_t own_addr_type;
struct ble_gap_disc_params disc_params;
int rc;
/* Figure out address to use while advertising (no privacy for now) */
rc = ble_hs_id_infer_auto(0, &own_addr_type);
if (rc != 0) {
ESP_LOGE(tag, "error determining address type; rc=%d", rc);
return;
}
/* Tell the controller to filter duplicates; we don't want to process
* repeated advertisements from the same device.
*/
disc_params.filter_duplicates = 1;
/**
* Perform a passive scan. I.e., don't send follow-up scan requests to
* each advertiser.
*/
disc_params.passive = 1;
/* Use defaults for the rest of the parameters. */
disc_params.itvl = 0;
disc_params.window = 0;
disc_params.filter_policy = 0;
disc_params.limited = 0;
rc = ble_gap_disc(own_addr_type, BLE_HS_FOREVER, &disc_params,
blecent_gap_event, NULL);
if (rc != 0) {
ESP_LOGE(tag, "Error initiating GAP discovery procedure; rc=%d\n",
rc);
}
}
/**
* Indicates whether we should try to connect to the sender of the specified
* advertisement. The function returns a positive result if the device
* advertises connectability and support for the THRPT service i.e. 0x0001.
*/
static int
blecent_should_connect(const struct ble_gap_disc_desc *disc)
{
struct ble_hs_adv_fields fields;
int rc;
int i;
rc = ble_hs_adv_parse_fields(&fields, disc->data, disc->length_data);
if (rc != 0) {
return rc;
}
if (strlen(CONFIG_EXAMPLE_PEER_ADDR) && (strncmp(CONFIG_EXAMPLE_PEER_ADDR, "ADDR_ANY", strlen("ADDR_ANY")) != 0)) {
ESP_LOGI(tag, "Peer address from menuconfig: %s", CONFIG_EXAMPLE_PEER_ADDR);
/* Convert string to address */
sscanf(CONFIG_EXAMPLE_PEER_ADDR, "%hhx:%hhx:%hhx:%hhx:%hhx:%hhx",
&peer_addr[5], &peer_addr[4], &peer_addr[3],
&peer_addr[2], &peer_addr[1], &peer_addr[0]);
if (memcmp(peer_addr, disc->addr.val, sizeof(disc->addr.val)) != 0) {
return 0;
}
}
ESP_LOGI(tag, "connect; fields.num_uuids128 =%d", fields.num_uuids128);
for (i = 0; i < fields.num_uuids128; i++) {
if ((memcmp(&fields.uuids128[i], THRPT_UUID_DECLARE(THRPT_SVC),
sizeof(ble_uuid128_t))) == 0 ) {
ESP_LOGI(tag, "blecent_should_connect 'THRPT' success");
return 1;
}
}
char serv_name[] = "nimble_prph";
if (fields.name != NULL) {
ESP_LOGI(tag, "Device Name = %s", (char *)fields.name);
if (memcmp(fields.name, serv_name, fields.name_len) == 0) {
ESP_LOGI(tag, "central connect to `nimble_prph` success");
return 1;
}
}
return 0;
}
/**
* Connects to the sender of the specified advertisement of it looks
* interesting. A device is "interesting" if it advertises connectability and
* support for the Alert Notification service.
*/
static void
blecent_connect_if_interesting(const struct ble_gap_disc_desc *disc)
{
uint8_t own_addr_type;
int rc;
/* Don't do anything if we don't care about this advertiser. */
if (!blecent_should_connect(disc)) {
return;
}
/* Scanning must be stopped before a connection can be initiated. */
rc = ble_gap_disc_cancel();
if (rc != 0) {
MODLOG_DFLT(DEBUG, "Failed to cancel scan; rc=%d\n", rc);
return;
}
/* Figure out address to use for connect (no privacy for now) */
rc = ble_hs_id_infer_auto(0, &own_addr_type);
if (rc != 0) {
ESP_LOGE(tag, "error determining address type; rc=%d\n", rc);
return;
}
/* Try to connect the the advertiser. Allow 30 seconds (30000 ms) for
* timeout.
*/
rc = ble_gap_connect(own_addr_type, &disc->addr, 30000, NULL,
blecent_gap_event, NULL);
if (rc != 0) {
ESP_LOGE(tag, "Error: Failed to connect to device; addr_type=%d "
"addr=%s; rc=%d\n",
disc->addr.type, addr_str(disc->addr.val), rc);
return;
}
}
/**
* The nimble host executes this callback when a GAP event occurs. The
* application associates a GAP event callback with each connection that is
* established. central uses the same callback for all connections.
*
* @param event The event being signalled.
* @param arg Application-specified argument; unused by
* gattc.
*
* @return 0 if the application successfully handled the
* event; nonzero on failure. The semantics
* of the return code is specific to the
* particular GAP event being signalled.
*/
static int
blecent_gap_event(struct ble_gap_event *event, void *arg)
{
struct ble_gap_conn_desc desc;
struct ble_hs_adv_fields fields;
int rc;
switch (event->type) {
case BLE_GAP_EVENT_DISC:
ESP_LOGI(tag, "Event DISC ");
rc = ble_hs_adv_parse_fields(&fields, event->disc.data,
event->disc.length_data);
if (rc != 0) {
return 0;
}
/* An advertisment report was received during GAP discovery. */
print_adv_fields(&fields);
/* Try to connect to the advertiser if it looks interesting. */
blecent_connect_if_interesting(&event->disc);
return 0;
case BLE_GAP_EVENT_CONNECT:
/* A new connection was established or a connection attempt failed. */
if (event->connect.status == 0) {
/* Connection successfully established. */
/* XXX Set packet length in controller for better throughput */
ESP_LOGI(tag, "Connection established ");
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; rc = %d", rc);
}
rc = ble_att_set_preferred_mtu(mtu_def);
if (rc != 0) {
ESP_LOGE(tag, "Failed to set preferred MTU; rc = %d", rc);
}
rc = ble_gattc_exchange_mtu(event->connect.conn_handle, NULL, NULL);
if (rc != 0) {
ESP_LOGE(tag, "Failed to negotiate MTU; rc = %d", rc);
}
rc = ble_gap_conn_find(event->connect.conn_handle, &desc);
assert(rc == 0);
print_conn_desc(&desc);
rc = ble_gap_update_params(event->connect.conn_handle, &conn_params);
if (rc != 0) {
ESP_LOGE(tag, "Failed to update params; rc = %d", rc);
}
/* Remember peer. */
rc = peer_add(event->connect.conn_handle);
if (rc != 0) {
ESP_LOGE(tag, "Failed to add peer; rc = %d", rc);
return 0;
}
/* Perform service discovery. */
rc = peer_disc_all(event->connect.conn_handle,
blecent_on_disc_complete, NULL);
if (rc != 0) {
ESP_LOGE(tag, "Failed to discover services; rc = %d", rc);
return 0;
}
} else {
/* Connection attempt failed; resume scanning. */
ESP_LOGE(tag, "Error: Connection failed; status = %d",
event->connect.status);
blecent_scan();
}
return 0;
case BLE_GAP_EVENT_DISCONNECT:
/* Connection terminated. */
ESP_LOGI(tag, "disconnect; reason=%d ", event->disconnect.reason);
print_conn_desc(&event->disconnect.conn);
ESP_LOGI(tag, "\n");
/* Forget about peer. */
peer_delete(event->disconnect.conn.conn_handle);
/* Resume scanning. */
blecent_scan();
return 0;
case BLE_GAP_EVENT_DISC_COMPLETE:
ESP_LOGI(tag, "discovery complete; reason = %d",
event->disc_complete.reason);
return 0;
case BLE_GAP_EVENT_ENC_CHANGE:
/* Encryption has been enabled or disabled for this connection. */
ESP_LOGI(tag, "encryption change event; status = %d ",
event->enc_change.status);
rc = ble_gap_conn_find(event->enc_change.conn_handle, &desc);
assert(rc == 0);
print_conn_desc(&desc);
return 0;
case BLE_GAP_EVENT_NOTIFY_RX:
/* Peer sent us a notification or indication. */
mbuf_len_total = mbuf_len_total + OS_MBUF_PKTLEN(event->notify_rx.om);
ESP_LOGI(tag, "received %s; conn_handle = %d attr_handle = %d "
"attr_len = %d ; Total length = %d",
event->notify_rx.indication ?
"indication" :
"notification",
event->notify_rx.conn_handle,
event->notify_rx.attr_handle,
OS_MBUF_PKTLEN(event->notify_rx.om),
mbuf_len_total);
/* Attribute data is contained in event->notify_rx.attr_data. */
return 0;
case BLE_GAP_EVENT_MTU:
ESP_LOGI(tag, "mtu update event; conn_handle = %d cid = %d mtu = %d",
event->mtu.conn_handle,
event->mtu.channel_id,
event->mtu.value);
return 0;
default:
return 0;
}
}
static void
blecent_on_reset(int reason)
{
ESP_LOGE(tag, "Resetting state; reason=%d\n", reason);
}
static void
blecent_on_sync(void)
{
int rc;
bool yes = false;
/* Make sure we have proper identity address set (public preferred) */
rc = ble_hs_util_ensure_addr(0);
assert(rc == 0);
ESP_LOGI(tag, "Do you want to configure connection params ? ");
ESP_LOGI(tag, "If yes then enter in this format: `Insert Yes` ");
if (scli_receive_yesno(&yes)) {
if (yes) {
ESP_LOGI(tag, " Enter preferred MTU, format:: `MTU 512` ");
if (scli_receive_key(&mtu_def)) {
ESP_LOGI(tag, "MTU provided by user= %d", mtu_def);
} else {
ESP_LOGD(tag, "No input for setting MTU; use default mtu = %d", mtu_def);
}
scli_reset_queue();
ESP_LOGI(tag, "For Conn param: Enter `min_itvl` `max_itvl`"
"`latency` `timeout` `min_ce` `max_ce` in same order");
ESP_LOGI(tag, "Enter conn_update in this format:: `conn 6 120 0 500 0 0`");
if (scli_receive_key(conn_params_def)) {
/** Minimum value for connection interval in 1.25ms units */
conn_params.itvl_min = conn_params_def[0];
/** Maximum value for connection interval in 1.25ms units */
conn_params.itvl_max = conn_params_def[1];
/** Connection latency */
conn_params.latency = conn_params_def[2];
/** Supervision timeout in 10ms units */
conn_params.supervision_timeout = conn_params_def[3];
/** Minimum length of connection event in 0.625ms units */
conn_params.min_ce_len = conn_params_def[4];
/** Maximum length of connection event in 0.625ms units */
conn_params.max_ce_len = conn_params_def[5];
} else {
ESP_LOGD(tag, "no input by user for conn param; use default ");
}
scli_reset_queue();
}
}
/* Begin scanning for a peripheral to connect to. */
blecent_scan();
}
void blecent_host_task(void *param)
{
ESP_LOGI(tag, "BLE Host Task Started");
xSemaphore = xSemaphoreCreateBinary();
xSemaphoreGive(xSemaphore);
/* This function will return only when nimble_port_stop() is executed */
nimble_port_run();
vSemaphoreDelete(xSemaphore);
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);
ESP_ERROR_CHECK(esp_nimble_hci_and_controller_init());
nimble_port_init();
/* Configure the host. */
ble_hs_cfg.reset_cb = blecent_on_reset;
ble_hs_cfg.sync_cb = blecent_on_sync;
ble_hs_cfg.store_status_cb = ble_store_util_status_rr;
/* Initialize data structures to track connected peers. */
rc = peer_init(MYNEWT_VAL(BLE_MAX_CONNECTIONS), 64, 64, 64);
assert(rc == 0);
/* Set the default device name. */
rc = ble_svc_gap_device_name_set("gattc-throughput");
assert(rc == 0);
/* XXX Need to have template for store */
ble_store_config_init();
/* Before starting with blecent host task, let us get CLI task up and
* running */
esp_console_repl_config_t repl_config = ESP_CONSOLE_REPL_CONFIG_DEFAULT();
esp_console_dev_uart_config_t uart_config = ESP_CONSOLE_DEV_UART_CONFIG_DEFAULT();
esp_console_repl_t *repl = NULL;
repl_config.prompt = "Throughput : ";
ESP_ERROR_CHECK(esp_console_new_repl_uart(&uart_config, &repl_config, &repl));
register_system();
esp_console_register_help_command();
/* Register commands */
ble_register_cli();
/* As BLE CLI has been registered, let us start nimble host task */
nimble_port_freertos_init(blecent_host_task);
printf("\n ===============================================================================================\n");
printf(" | Steps to test nimble throughput |\n");
printf(" | |\n");
printf(" | 1. Print 'help' to gain overview of commands |\n");
printf(" | |\n");
printf("\n ===============================================================================================\n");
const char *prompt = LOG_COLOR_I "Throughput demo >> " LOG_RESET_COLOR;
while (true) {
/* Get a line using linenoise.
* The line is returned when ENTER is pressed.
*/
char *line = linenoise(prompt);
if (line == NULL) { /* Ignore empty lines */
continue;
}
/* Add the command to the history */
linenoiseHistoryAdd(line);
/* Try to run the command */
int ret;
esp_err_t err = esp_console_run(line, &ret);
if (err == ESP_ERR_NOT_FOUND) {
printf("Unrecognized command\n");
} else if (err == ESP_ERR_INVALID_ARG) {
// command was empty
} else if (err == ESP_OK && ret != ESP_OK) {
printf("Command returned non-zero error code: 0x%x (%s)\n", ret, esp_err_to_name(ret));
} else if (err != ESP_OK) {
printf("Internal error: %s\n", esp_err_to_name(err));
}
/* linenoise allocates line buffer on the heap, so need to free it */
linenoiseFree(line);
}
}

View File

@ -0,0 +1,211 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#include <assert.h>
#include <stdio.h>
#include <string.h>
#include "host/ble_hs.h"
#include "host/ble_uuid.h"
#include "gattc.h"
/**
* 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) {
assert(fields->name_len < sizeof s - 1);
memcpy(s, fields->name, fields->name_len);
s[fields->name_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");
}
}

View File

@ -0,0 +1,808 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#include <assert.h>
#include <string.h>
#include "host/ble_hs.h"
#include "gattc.h"
static void *peer_svc_mem;
static struct os_mempool peer_svc_pool;
static void *peer_chr_mem;
static struct os_mempool peer_chr_pool;
static void *peer_dsc_mem;
static struct os_mempool peer_dsc_pool;
static void *peer_mem;
static struct os_mempool peer_pool;
static SLIST_HEAD(, peer) peers;
static struct peer_svc *
peer_svc_find_range(struct peer *peer, uint16_t attr_handle);
static struct peer_svc *
peer_svc_find(struct peer *peer, uint16_t svc_start_handle,
struct peer_svc **out_prev);
int
peer_svc_is_empty(const struct peer_svc *svc);
uint16_t
chr_end_handle(const struct peer_svc *svc, const struct peer_chr *chr);
int
chr_is_empty(const struct peer_svc *svc, const struct peer_chr *chr);
static struct peer_chr *
peer_chr_find(const struct peer_svc *svc, uint16_t chr_def_handle,
struct peer_chr **out_prev);
static void
peer_disc_chrs(struct peer *peer);
static int
peer_dsc_disced(uint16_t conn_handle, const struct ble_gatt_error *error,
uint16_t chr_val_handle, const struct ble_gatt_dsc *dsc,
void *arg);
struct peer *
peer_find(uint16_t conn_handle)
{
struct peer *peer;
SLIST_FOREACH(peer, &peers, next) {
if (peer->conn_handle == conn_handle) {
return peer;
}
}
return NULL;
}
static void
peer_disc_complete(struct peer *peer, int rc)
{
peer->disc_prev_chr_val = 0;
/* Notify caller that discovery has completed. */
if (peer->disc_cb != NULL) {
peer->disc_cb(peer, rc, peer->disc_cb_arg);
}
}
static struct peer_dsc *
peer_dsc_find_prev(const struct peer_chr *chr, uint16_t dsc_handle)
{
struct peer_dsc *prev;
struct peer_dsc *dsc;
prev = NULL;
SLIST_FOREACH(dsc, &chr->dscs, next) {
if (dsc->dsc.handle >= dsc_handle) {
break;
}
prev = dsc;
}
return prev;
}
static struct peer_dsc *
peer_dsc_find(const struct peer_chr *chr, uint16_t dsc_handle,
struct peer_dsc **out_prev)
{
struct peer_dsc *prev;
struct peer_dsc *dsc;
prev = peer_dsc_find_prev(chr, dsc_handle);
if (prev == NULL) {
dsc = SLIST_FIRST(&chr->dscs);
} else {
dsc = SLIST_NEXT(prev, next);
}
if (dsc != NULL && dsc->dsc.handle != dsc_handle) {
dsc = NULL;
}
if (out_prev != NULL) {
*out_prev = prev;
}
return dsc;
}
static int
peer_dsc_add(struct peer *peer, uint16_t chr_val_handle,
const struct ble_gatt_dsc *gatt_dsc)
{
struct peer_dsc *prev;
struct peer_dsc *dsc;
struct peer_svc *svc;
struct peer_chr *chr;
svc = peer_svc_find_range(peer, chr_val_handle);
if (svc == NULL) {
/* Can't find service for discovered descriptor; this shouldn't
* happen.
*/
assert(0);
return BLE_HS_EUNKNOWN;
}
chr = peer_chr_find(svc, chr_val_handle, NULL);
if (chr == NULL) {
/* Can't find characteristic for discovered descriptor; this shouldn't
* happen.
*/
assert(0);
return BLE_HS_EUNKNOWN;
}
dsc = peer_dsc_find(chr, gatt_dsc->handle, &prev);
if (dsc != NULL) {
/* Descriptor already discovered. */
return 0;
}
dsc = os_memblock_get(&peer_dsc_pool);
if (dsc == NULL) {
/* Out of memory. */
return BLE_HS_ENOMEM;
}
memset(dsc, 0, sizeof * dsc);
dsc->dsc = *gatt_dsc;
if (prev == NULL) {
SLIST_INSERT_HEAD(&chr->dscs, dsc, next);
} else {
SLIST_NEXT(prev, next) = dsc;
}
return 0;
}
static void
peer_disc_dscs(struct peer *peer)
{
struct peer_chr *chr;
struct peer_svc *svc;
int rc;
/* Search through the list of discovered characteristics for the first
* characteristic that contains undiscovered descriptors. Then, discover
* all descriptors belonging to that characteristic.
*/
SLIST_FOREACH(svc, &peer->svcs, next) {
SLIST_FOREACH(chr, &svc->chrs, next) {
if (!chr_is_empty(svc, chr) &&
SLIST_EMPTY(&chr->dscs) &&
peer->disc_prev_chr_val <= chr->chr.def_handle) {
rc = ble_gattc_disc_all_dscs(peer->conn_handle,
chr->chr.val_handle,
chr_end_handle(svc, chr),
peer_dsc_disced, peer);
if (rc != 0) {
peer_disc_complete(peer, rc);
}
peer->disc_prev_chr_val = chr->chr.val_handle;
return;
}
}
}
/* All descriptors discovered. */
peer_disc_complete(peer, 0);
}
static int
peer_dsc_disced(uint16_t conn_handle, const struct ble_gatt_error *error,
uint16_t chr_val_handle, const struct ble_gatt_dsc *dsc,
void *arg)
{
struct peer *peer;
int rc;
peer = arg;
assert(peer->conn_handle == conn_handle);
switch (error->status) {
case 0:
rc = peer_dsc_add(peer, chr_val_handle, dsc);
break;
case BLE_HS_EDONE:
/* All descriptors in this characteristic discovered; start discovering
* descriptors in the next characteristic.
*/
if (peer->disc_prev_chr_val > 0) {
peer_disc_dscs(peer);
}
rc = 0;
break;
default:
/* Error; abort discovery. */
rc = error->status;
break;
}
if (rc != 0) {
/* Error; abort discovery. */
peer_disc_complete(peer, rc);
}
return rc;
}
uint16_t
chr_end_handle(const struct peer_svc *svc, const struct peer_chr *chr)
{
const struct peer_chr *next_chr;
next_chr = SLIST_NEXT(chr, next);
if (next_chr != NULL) {
return next_chr->chr.def_handle - 1;
} else {
return svc->svc.end_handle;
}
}
int
chr_is_empty(const struct peer_svc *svc, const struct peer_chr *chr)
{
return chr_end_handle(svc, chr) <= chr->chr.val_handle;
}
static struct peer_chr *
peer_chr_find_prev(const struct peer_svc *svc, uint16_t chr_val_handle)
{
struct peer_chr *prev;
struct peer_chr *chr;
prev = NULL;
SLIST_FOREACH(chr, &svc->chrs, next) {
if (chr->chr.val_handle >= chr_val_handle) {
break;
}
prev = chr;
}
return prev;
}
static struct peer_chr *
peer_chr_find(const struct peer_svc *svc, uint16_t chr_val_handle,
struct peer_chr **out_prev)
{
struct peer_chr *prev;
struct peer_chr *chr;
prev = peer_chr_find_prev(svc, chr_val_handle);
if (prev == NULL) {
chr = SLIST_FIRST(&svc->chrs);
} else {
chr = SLIST_NEXT(prev, next);
}
if (chr != NULL && chr->chr.val_handle != chr_val_handle) {
chr = NULL;
}
if (out_prev != NULL) {
*out_prev = prev;
}
return chr;
}
static void
peer_chr_delete(struct peer_chr *chr)
{
struct peer_dsc *dsc;
while ((dsc = SLIST_FIRST(&chr->dscs)) != NULL) {
SLIST_REMOVE_HEAD(&chr->dscs, next);
os_memblock_put(&peer_dsc_pool, dsc);
}
os_memblock_put(&peer_chr_pool, chr);
}
static int
peer_chr_add(struct peer *peer, uint16_t svc_start_handle,
const struct ble_gatt_chr *gatt_chr)
{
struct peer_chr *prev;
struct peer_chr *chr;
struct peer_svc *svc;
svc = peer_svc_find(peer, svc_start_handle, NULL);
if (svc == NULL) {
/* Can't find service for discovered characteristic; this shouldn't
* happen.
*/
assert(0);
return BLE_HS_EUNKNOWN;
}
chr = peer_chr_find(svc, gatt_chr->def_handle, &prev);
if (chr != NULL) {
/* Characteristic already discovered. */
return 0;
}
chr = os_memblock_get(&peer_chr_pool);
if (chr == NULL) {
/* Out of memory. */
return BLE_HS_ENOMEM;
}
memset(chr, 0, sizeof * chr);
chr->chr = *gatt_chr;
if (prev == NULL) {
SLIST_INSERT_HEAD(&svc->chrs, chr, next);
} else {
SLIST_NEXT(prev, next) = chr;
}
return 0;
}
static int
peer_chr_disced(uint16_t conn_handle, const struct ble_gatt_error *error,
const struct ble_gatt_chr *chr, void *arg)
{
struct peer *peer;
int rc;
peer = arg;
assert(peer->conn_handle == conn_handle);
switch (error->status) {
case 0:
rc = peer_chr_add(peer, peer->cur_svc->svc.start_handle, chr);
break;
case BLE_HS_EDONE:
/* All characteristics in this service discovered; start discovering
* characteristics in the next service.
*/
if (peer->disc_prev_chr_val > 0) {
peer_disc_chrs(peer);
}
rc = 0;
break;
default:
rc = error->status;
break;
}
if (rc != 0) {
/* Error; abort discovery. */
peer_disc_complete(peer, rc);
}
return rc;
}
static void
peer_disc_chrs(struct peer *peer)
{
struct peer_svc *svc;
int rc;
/* Search through the list of discovered service for the first service that
* contains undiscovered characteristics. Then, discover all
* characteristics belonging to that service.
*/
SLIST_FOREACH(svc, &peer->svcs, next) {
if (!peer_svc_is_empty(svc) && SLIST_EMPTY(&svc->chrs)) {
peer->cur_svc = svc;
rc = ble_gattc_disc_all_chrs(peer->conn_handle,
svc->svc.start_handle,
svc->svc.end_handle,
peer_chr_disced, peer);
if (rc != 0) {
peer_disc_complete(peer, rc);
}
return;
}
}
/* All characteristics discovered. */
peer_disc_dscs(peer);
}
int
peer_svc_is_empty(const struct peer_svc *svc)
{
return svc->svc.end_handle <= svc->svc.start_handle;
}
static struct peer_svc *
peer_svc_find_prev(struct peer *peer, uint16_t svc_start_handle)
{
struct peer_svc *prev;
struct peer_svc *svc;
prev = NULL;
SLIST_FOREACH(svc, &peer->svcs, next) {
if (svc->svc.start_handle >= svc_start_handle) {
break;
}
prev = svc;
}
return prev;
}
static struct peer_svc *
peer_svc_find(struct peer *peer, uint16_t svc_start_handle,
struct peer_svc **out_prev)
{
struct peer_svc *prev;
struct peer_svc *svc;
prev = peer_svc_find_prev(peer, svc_start_handle);
if (prev == NULL) {
svc = SLIST_FIRST(&peer->svcs);
} else {
svc = SLIST_NEXT(prev, next);
}
if (svc != NULL && svc->svc.start_handle != svc_start_handle) {
svc = NULL;
}
if (out_prev != NULL) {
*out_prev = prev;
}
return svc;
}
static struct peer_svc *
peer_svc_find_range(struct peer *peer, uint16_t attr_handle)
{
struct peer_svc *svc;
SLIST_FOREACH(svc, &peer->svcs, next) {
if (svc->svc.start_handle <= attr_handle &&
svc->svc.end_handle >= attr_handle) {
return svc;
}
}
return NULL;
}
const struct peer_svc *
peer_svc_find_uuid(const struct peer *peer, const ble_uuid_t *uuid)
{
const struct peer_svc *svc;
SLIST_FOREACH(svc, &peer->svcs, next) {
if ((uuid != NULL) && (ble_uuid_cmp(&(svc->svc.uuid.u), uuid) == 0)) {
return svc;
}
}
return NULL;
}
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 *svc;
const struct peer_chr *chr;
svc = peer_svc_find_uuid(peer, svc_uuid);
if (svc == NULL) {
return NULL;
}
SLIST_FOREACH(chr, &svc->chrs, next) {
if ((chr_uuid != NULL) && (ble_uuid_cmp(&chr->chr.uuid.u, chr_uuid) == 0)) {
return chr;
}
}
return NULL;
}
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 *chr;
const struct peer_dsc *dsc;
chr = peer_chr_find_uuid(peer, svc_uuid, chr_uuid);
if (chr == NULL) {
return NULL;
}
SLIST_FOREACH(dsc, &chr->dscs, next) {
if ((dsc_uuid != NULL) && (ble_uuid_cmp(&dsc->dsc.uuid.u, dsc_uuid) == 0)) {
return dsc;
}
}
return NULL;
}
static int
peer_svc_add(struct peer *peer, const struct ble_gatt_svc *gatt_svc)
{
struct peer_svc *prev;
struct peer_svc *svc;
svc = peer_svc_find(peer, gatt_svc->start_handle, &prev);
if (svc != NULL) {
/* Service already discovered. */
return 0;
}
svc = os_memblock_get(&peer_svc_pool);
if (svc == NULL) {
/* Out of memory. */
return BLE_HS_ENOMEM;
}
memset(svc, 0, sizeof * svc);
svc->svc = *gatt_svc;
SLIST_INIT(&svc->chrs);
if (prev == NULL) {
SLIST_INSERT_HEAD(&peer->svcs, svc, next);
} else {
SLIST_INSERT_AFTER(prev, svc, next);
}
return 0;
}
static void
peer_svc_delete(struct peer_svc *svc)
{
struct peer_chr *chr;
while ((chr = SLIST_FIRST(&svc->chrs)) != NULL) {
SLIST_REMOVE_HEAD(&svc->chrs, next);
peer_chr_delete(chr);
}
os_memblock_put(&peer_svc_pool, svc);
}
static int
peer_svc_disced(uint16_t conn_handle, const struct ble_gatt_error *error,
const struct ble_gatt_svc *service, void *arg)
{
struct peer *peer;
int rc;
peer = arg;
assert(peer->conn_handle == conn_handle);
switch (error->status) {
case 0:
rc = peer_svc_add(peer, service);
break;
case BLE_HS_EDONE:
/* All services discovered; start discovering characteristics. */
if (peer->disc_prev_chr_val > 0) {
peer_disc_chrs(peer);
}
rc = 0;
break;
default:
rc = error->status;
break;
}
if (rc != 0) {
/* Error; abort discovery. */
peer_disc_complete(peer, rc);
}
return rc;
}
int
peer_disc_all(uint16_t conn_handle, peer_disc_fn *disc_cb, void *disc_cb_arg)
{
struct peer_svc *svc;
struct peer *peer;
int rc;
peer = peer_find(conn_handle);
if (peer == NULL) {
return BLE_HS_ENOTCONN;
}
/* Undiscover everything first. */
while ((svc = SLIST_FIRST(&peer->svcs)) != NULL) {
SLIST_REMOVE_HEAD(&peer->svcs, next);
peer_svc_delete(svc);
}
peer->disc_prev_chr_val = 1;
peer->disc_cb = disc_cb;
peer->disc_cb_arg = disc_cb_arg;
rc = ble_gattc_disc_all_svcs(conn_handle, peer_svc_disced, peer);
if (rc != 0) {
return rc;
}
return 0;
}
int
peer_delete(uint16_t conn_handle)
{
struct peer_svc *svc;
struct peer *peer;
int rc;
peer = peer_find(conn_handle);
if (peer == NULL) {
return BLE_HS_ENOTCONN;
}
SLIST_REMOVE(&peers, peer, peer, next);
while ((svc = SLIST_FIRST(&peer->svcs)) != NULL) {
SLIST_REMOVE_HEAD(&peer->svcs, next);
peer_svc_delete(svc);
}
rc = os_memblock_put(&peer_pool, peer);
if (rc != 0) {
return BLE_HS_EOS;
}
return 0;
}
int
peer_add(uint16_t conn_handle)
{
struct peer *peer;
/* Make sure the connection handle is unique. */
peer = peer_find(conn_handle);
if (peer != NULL) {
return BLE_HS_EALREADY;
}
peer = os_memblock_get(&peer_pool);
if (peer == NULL) {
/* Out of memory. */
return BLE_HS_ENOMEM;
}
memset(peer, 0, sizeof * peer);
peer->conn_handle = conn_handle;
SLIST_INSERT_HEAD(&peers, peer, next);
return 0;
}
static void
peer_free_mem(void)
{
free(peer_mem);
peer_mem = NULL;
free(peer_svc_mem);
peer_svc_mem = NULL;
free(peer_chr_mem);
peer_chr_mem = NULL;
free(peer_dsc_mem);
peer_dsc_mem = NULL;
}
int
peer_init(int max_peers, int max_svcs, int max_chrs, int max_dscs)
{
int rc;
/* Free memory first in case this function gets called more than once. */
peer_free_mem();
peer_mem = malloc(
OS_MEMPOOL_BYTES(max_peers, sizeof (struct peer)));
if (peer_mem == NULL) {
rc = BLE_HS_ENOMEM;
goto err;
}
rc = os_mempool_init(&peer_pool, max_peers,
sizeof (struct peer), peer_mem,
"peer_pool");
if (rc != 0) {
rc = BLE_HS_EOS;
goto err;
}
peer_svc_mem = malloc(
OS_MEMPOOL_BYTES(max_svcs, sizeof (struct peer_svc)));
if (peer_svc_mem == NULL) {
rc = BLE_HS_ENOMEM;
goto err;
}
rc = os_mempool_init(&peer_svc_pool, max_svcs,
sizeof (struct peer_svc), peer_svc_mem,
"peer_svc_pool");
if (rc != 0) {
rc = BLE_HS_EOS;
goto err;
}
peer_chr_mem = malloc(
OS_MEMPOOL_BYTES(max_chrs, sizeof (struct peer_chr)));
if (peer_chr_mem == NULL) {
rc = BLE_HS_ENOMEM;
goto err;
}
rc = os_mempool_init(&peer_chr_pool, max_chrs,
sizeof (struct peer_chr), peer_chr_mem,
"peer_chr_pool");
if (rc != 0) {
rc = BLE_HS_EOS;
goto err;
}
peer_dsc_mem = malloc(
OS_MEMPOOL_BYTES(max_dscs, sizeof (struct peer_dsc)));
if (peer_dsc_mem == NULL) {
rc = BLE_HS_ENOMEM;
goto err;
}
rc = os_mempool_init(&peer_dsc_pool, max_dscs,
sizeof (struct peer_dsc), peer_dsc_mem,
"peer_dsc_pool");
if (rc != 0) {
rc = BLE_HS_EOS;
goto err;
}
return 0;
err:
peer_free_mem();
ESP_LOGE("ERROR", "Error while allocating mem");
return rc;
}

View File

@ -0,0 +1,194 @@
/*
* Copyright 2020 Espressif Systems (Shanghai) PTE LTD
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#include <stdio.h>
#include <ctype.h>
#include "esp_log.h"
#include <string.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;
static int key[2];
static int conn_param[10];
static int mtu;
#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;
}
sscanf(argv[1], "%d", &conn_param[0]);
sscanf(argv[2], "%d", &conn_param[1]);
sscanf(argv[3], "%d", &conn_param[2]);
sscanf(argv[4], "%d", &conn_param[3]);
sscanf(argv[5], "%d", &conn_param[4]);
sscanf(argv[6], "%d", &conn_param[5]);
ESP_LOGI("You entered", "%s %d %d %d %d %d %d", argv[0], conn_param[0], conn_param[1],
conn_param[2], conn_param[3], conn_param[4], conn_param[5]);
xQueueSend(cli_handle, &conn_param[0], 500 / portTICK_PERIOD_MS);
return 0;
}
static int conn_mtu_handler(int argc, char *argv[])
{
ESP_LOGI("MTU Arguments entered", "%d", argc);
if (argc != 2) {
return -1;
}
sscanf(argv[1], "%d", &mtu);
ESP_LOGI("You entered", "%s %d", argv[0], mtu);
xQueueSend(cli_handle, &mtu, 500 / portTICK_PERIOD_MS);
return 0;
}
static int throughput_demo_handler(int argc, char *argv[])
{
char pkey[8];
if (argc != 3) {
return -1;
}
sscanf(argv[1], "%s", pkey);
if (strcmp(pkey, "read") == 0) {
key[0] = 1;
} else if (strcmp(pkey, "write") == 0) {
key[0] = 2;
} else if (strcmp(pkey, "notify") == 0) {
key[0] = 3;
} else {
key[0] = 0;
}
sscanf(argv[2], "%d", &key[1]);
ESP_LOGI("Throughput demo handler", "%s %s %d", argv[0], argv[1], key[1]);
xQueueSend(cli_handle, &key[0], 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], "%s", yesno);
if (strcmp(yesno, "Yes") || strcmp (yesno, "YES") || strcmp(yesno, "yes")) {
yes = 1;
} else {
yes = 0;
}
ESP_LOGI("User entered", "%s %s", argv[0], yesno);
xQueueSend(cli_handle, &yes, 500 / portTICK_PERIOD_MS);
return 0;
}
int scli_receive_yesno(bool *console_key)
{
return xQueueReceive(cli_handle, console_key, YES_NO_PARAM);
}
int scli_receive_key(int *console_key)
{
return xQueueReceive(cli_handle, console_key, BLE_RX_PARAM);
}
int cli_receive_key(int *console_key)
{
return xQueueReceive(cli_handle, console_key, BLE_RX_TIMEOUT);
}
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(int) * 6);
if (cli_handle == NULL) {
return;
}
ESP_LOGI("CLI", "BLE CLI registered ");
return;
}

View File

@ -0,0 +1,15 @@
# Override some defaults so BT stack is enabled
# in this example and some misc buffer sizes are increased
#
# BT config
#
CONFIG_BT_ENABLED=y
CONFIG_BTDM_CTRL_MODE_BLE_ONLY=y
CONFIG_BTDM_CTRL_MODE_BR_EDR_ONLY=n
CONFIG_BTDM_CTRL_MODE_BTDM=n
CONFIG_BT_BLUEDROID_ENABLED=n
CONFIG_BT_NIMBLE_ENABLED=y
CONFIG_BT_NIMBLE_ATT_PREFERRED_MTU=512
CONFIG_BT_NIMBLE_ACL_BUF_COUNT=20
CONFIG_BT_NIMBLE_HCI_EVT_BUF_SIZE=255

View File

@ -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.5)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(bleprph_throughput)

View File

@ -0,0 +1,8 @@
#
# This is a project Makefile. It is assumed the directory this Makefile resides in is a
# project subdirectory.
#
PROJECT_NAME := bleprph_throughput
include $(IDF_PATH)/make/project.mk

View File

@ -0,0 +1,63 @@
# 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 (`PTS_LONG_CHR_READ_WRITE`,`PTS_CHR_READ_WRITE`,`PTS_CHR_NOTIFY`). The data of 500 Bytes (`READ_THROUGHPUT_PAYLOAD`) and 400 Bytes (`WRITE_THROUGHPUT_PAYLOAD`) is transferred for throughput GATT read and write operations respectively.
`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` tries to demonstrate stable implementation of GATT operations like read/write and notify. For `bleprph_throughput` app, notifications are sent almost continuously for stipulated period of time. The almost part is because we use counting semaphore (~100) to mimic continuous notifications. Here one needs to understand that notifications are sent in `os_mbufs` packets and there can always be chance of them getting full because of continuous operation, so one may need to allocate higher number of mbufs through menuconfig, whenever there is `os_mbuf` memory exhaustion, app provides delay so NimBLE host stack can breathe and free `mbuf chains`.

View File

@ -0,0 +1,2 @@
idf_component_register(SRCS "main.c" "gatt_svr.c"
INCLUDE_DIRS ".")

View File

@ -0,0 +1,4 @@
#
# "main" pseudo-component makefile.
#
# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.)

View File

@ -0,0 +1,227 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#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 500
#define WRITE_THROUGHPUT_PAYLOAD 500
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 = &notify_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) {
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\n",
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\n",
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;
ble_svc_gap_init();
ble_svc_gatt_init();
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;
}

View File

@ -0,0 +1,42 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#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

View File

@ -0,0 +1,410 @@
/*
* Copyright 2020 Espressif Systems (Shanghai) PTE LTD
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#include "esp_log.h"
#include "nvs_flash.h"
#include "freertos/FreeRTOSConfig.h"
/* BLE */
#include "esp_nimble_hci.h"
#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"
#define NOTIFY_THROUGHPUT_PAYLOAD 500
#define MIN_REQUIRED_MBUF 2 /* Assuming payload of 500Bytes and each mbuf can take 292Bytes. */
#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 const char *device_name = "nimble_prph";
static SemaphoreHandle_t notify_sem;
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);
/**
* 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);
}
/*
* 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\n", 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\n", rc);
return;
}
}
/* 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_time < (notify_test_time * 1000)) {
/* We are anyway using counting semaphore for sending
* notifications. So hopefully not much waiting period will be
* introduced before sending a new notification. Revisit this
* counter if need to do away with semaphore waiting. XXX */
xSemaphoreTake(notify_sem, portMAX_DELAY);
if (dummy == 200) {
dummy = 0;
}
dummy++;
/* Check if the MBUFs are available */
if (os_msys_num_free() >= MIN_REQUIRED_MBUF) {
om = ble_hs_mbuf_from_flat(payload, sizeof(payload));
if (om == NULL) {
/* Memory not available for mbuf */
ESP_LOGE(tag, "No MBUFs available from pool, retry..");
vTaskDelay(100 / portTICK_PERIOD_MS);
om = ble_hs_mbuf_from_flat(payload, sizeof(payload));
assert(om != NULL);
}
rc = ble_gattc_notify_custom(conn_handle, notify_handle, om);
if (rc != 0) {
ESP_LOGE(tag, "Error while sending notification; rc = %d", rc);
notify_count -= 1;
xSemaphoreGive(notify_sem);
/* Most probably error is because we ran out of mbufs (rc = 6),
* increase the mbuf count/size from menuconfig. Though
* inserting delay is not good solution let us keep it
* simple for time being so that the mbufs get freed up
* (?), of course assumption is we ran out of mbufs */
vTaskDelay(10 / portTICK_PERIOD_MS);
}
} else {
ESP_LOGE(tag, "Not enough OS_MBUFs available; reduce notify count ");
xSemaphoreGive(notify_sem);
notify_count -= 1;
}
end_time = esp_timer_get_time();
notify_time = (end_time - start_time) / 1000 ;
notify_count += 1;
}
printf("\n*********************************\n");
ESP_LOGI(tag, "Notify throughput = %d bps, count = %d",
(notify_count * NOTIFY_THROUGHPUT_PAYLOAD * 8) / notify_test_time, notify_count);
printf("\n*********************************\n");
ESP_LOGI(tag, " Notification test complete for stipulated time of %d sec", notify_test_time);
notify_test_time = 0;
notify_count = 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 */
gatts_advertise();
}
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);
/* Connection terminated; resume advertising */
gatts_advertise();
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 ");
gatts_advertise();
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 (arg != NULL) {
ESP_LOGI(tag, "notify test time = %d", *(int *)arg);
notify_test_time = *((int *)arg);
}
xSemaphoreGive(notify_sem);
} 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 !!");
if ((event->notify_tx.status == 0) ||
(event->notify_tx.status == BLE_HS_EDONE)) {
/* Send new notification i.e. give Semaphore. By definition,
* sending new notifications should not be based on successful
* notifications sent, but let us adopt this method to avoid too
* many `BLE_HS_ENOMEM` errors because of continuous transfer of
* notifications.XXX */
xSemaphoreGive(notify_sem);
} 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;
}
return 0;
}
static void
gatts_on_sync(void)
{
int rc;
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 */
gatts_advertise();
}
static void
gatts_on_reset(int reason)
{
ESP_LOGE(tag, "Resetting state; reason=%d\n", reason);
}
void gatts_host_task(void *param)
{
ESP_LOGI(tag, "BLE Host Task Started");
/* Create a counting semaphore for Notification. Can be used to track
* successful notification txmission. Optimistically take some big number
* for counting Semaphore */
notify_sem = xSemaphoreCreateCounting(100, 0);
/* This function will return only when nimble_port_stop() is executed */
nimble_port_run();
vSemaphoreDelete(notify_sem);
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);
ESP_ERROR_CHECK(esp_nimble_hci_and_controller_init());
nimble_port_init();
/* 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 */
xTaskCreate(notify_task, "notify_task", 4096, NULL, 10, NULL);
rc = gatt_svr_init();
assert(rc == 0);
/* Set the default device name */
rc = ble_svc_gap_device_name_set(device_name);
assert(rc == 0);
/* Start the task */
nimble_port_freertos_init(gatts_host_task);
}

View File

@ -0,0 +1,16 @@
# Override some defaults so BT stack is enabled
# in this example and some example specific misc sizes are increased.
#
# BT config
#
CONFIG_BT_ENABLED=y
CONFIG_BTDM_CTRL_MODE_BLE_ONLY=y
CONFIG_BTDM_CTRL_MODE_BR_EDR_ONLY=n
CONFIG_BTDM_CTRL_MODE_BTDM=n
CONFIG_BT_BLUEDROID_ENABLED=n
CONFIG_BT_NIMBLE_ENABLED=y
CONFIG_BT_NIMBLE_ATT_PREFERRED_MTU=512
CONFIG_BT_NIMBLE_ACL_BUF_COUNT=20
CONFIG_BT_NIMBLE_HCI_EVT_BUF_SIZE=255
CONFIG_BT_NIMBLE_MSYS1_BLOCK_COUNT=50