Bluetooth: Add a sample to demonstrate MTU update

Add a new sample that demonstrate how to exchange MTU to allow larger
packet transmission.

The sample is split in two applications. One is the Central, it will
initiate the MTU exchange. The other one is the Peripheral and will try to
send a large notification. If the MTU exchange fail or the new size is not
big enough, the Peripheral will not be able to send the notification.

Signed-off-by: Théo Battrel <theo.battrel@nordicsemi.no>
This commit is contained in:
Théo Battrel 2022-12-07 16:01:04 +01:00 committed by Carles Cufí
commit 7e23294bc5
11 changed files with 469 additions and 0 deletions

View file

@ -0,0 +1,21 @@
.. _bluetooth_mtu_update_sample:
Bluetooth: MTU Update
#####################
Overview:
*********
Application demonstrating the exchange of MTU between two devices to allow a
large notification to be sent.
Updating the MTU is useful to send bigger packets and so have a better
throughput.
Building and Running
********************
This sample can be found under :zephyr_file:`samples/bluetooth/mtu_update` in
the Zephyr tree.
See :ref:`bluetooth samples section <bluetooth-samples>` for details.

View file

@ -0,0 +1,13 @@
# SPDX-License-Identifier: Apache-2.0
cmake_minimum_required(VERSION 3.20.0)
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
project(mtu_update)
target_sources(app PRIVATE
src/main.c
src/central_mtu_update.c
)
zephyr_library_include_directories(${ZEPHYR_BASE}/samples/bluetooth)

View file

@ -0,0 +1,15 @@
CONFIG_BT=y
CONFIG_BT_CENTRAL=y
CONFIG_BT_DEVICE_NAME="Zephyr Central MTU Update Sample"
CONFIG_BT_GATT_CLIENT=y
CONFIG_LOG=y
CONFIG_BT_L2CAP_LOG_LEVEL_DBG=y
# HCI ACL buffers size
# BT_L2CAP_RX_MTU = CONFIG_BT_BUF_ACL_RX_SIZE - BT_L2CAP_HDR_SIZE
CONFIG_BT_BUF_ACL_RX_SIZE=251
# L2CAP SDU/PDU TX MTU
CONFIG_BT_L2CAP_TX_MTU=247

View file

@ -0,0 +1,9 @@
sample:
name: Bluetooth Central MTU Update
tests:
sample.bluetooth.central_mtu_update:
harness: bluetooth
platform_allow: qemu_cortex_m3 qemu_x86 nrf52_bsim
tags: bluetooth
integration_platforms:
- qemu_cortex_m3

View file

@ -0,0 +1,263 @@
/*
* Copyright (c) 2023 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stddef.h>
#include <errno.h>
#include <stdint.h>
#include <string.h>
#include <zephyr/types.h>
#include <zephyr/kernel.h>
#include <zephyr/sys/printk.h>
#include <zephyr/sys/byteorder.h>
#include <zephyr/bluetooth/att.h>
#include <zephyr/bluetooth/bluetooth.h>
#include <zephyr/bluetooth/hci.h>
#include <zephyr/bluetooth/conn.h>
#include <zephyr/bluetooth/uuid.h>
#include <zephyr/bluetooth/gatt.h>
#define MTU_TEST_SERVICE_TYPE BT_UUID_128_ENCODE(0x2e2b8dc3, 0x06e0, 0x4f93, 0x9bb2, 0x734091c356f0)
#define MTU_TEST_SERVICE_NOTIFY_TYPE \
BT_UUID_128_ENCODE(0x2e2b8dc3, 0x06e0, 0x4f93, 0x9bb2, 0x734091c356f3)
#define BT_UUID_MTU_TEST BT_UUID_DECLARE_128(MTU_TEST_SERVICE_TYPE)
#define BT_UUID_MTU_TEST_NOTIFY BT_UUID_DECLARE_128(MTU_TEST_SERVICE_NOTIFY_TYPE)
static void start_scan(void);
static struct bt_conn *default_conn;
static struct bt_uuid_128 uuid = BT_UUID_INIT_128(0);
static struct bt_gatt_discover_params discover_params;
static struct bt_gatt_subscribe_params subscribe_params;
bt_gatt_notify_func_t notify_cb;
static uint8_t notify_func(struct bt_conn *conn,
struct bt_gatt_subscribe_params *params,
const void *data, uint16_t length)
{
if (!data) {
printk("[UNSUBSCRIBED]\n");
params->value_handle = 0U;
return BT_GATT_ITER_STOP;
}
printk("[NOTIFICATION] data %p length %u\n", data, length);
if (notify_cb != NULL) {
notify_cb(conn, params, data, length);
}
return BT_GATT_ITER_STOP;
}
static uint8_t discover_func(struct bt_conn *conn, const struct bt_gatt_attr *attr,
struct bt_gatt_discover_params *params)
{
int err;
if (!attr) {
printk("Discover complete\n");
(void)memset(params, 0, sizeof(*params));
return BT_GATT_ITER_STOP;
}
printk("[ATTRIBUTE] handle %u\n", attr->handle);
if (!bt_uuid_cmp(discover_params.uuid, BT_UUID_MTU_TEST)) {
memcpy(&uuid, BT_UUID_MTU_TEST_NOTIFY, sizeof(uuid));
discover_params.uuid = &uuid.uuid;
discover_params.start_handle = attr->handle + 1;
discover_params.type = BT_GATT_DISCOVER_CHARACTERISTIC;
err = bt_gatt_discover(conn, &discover_params);
if (err) {
printk("Discover failed (err %d)\n", err);
}
} else if (!bt_uuid_cmp(discover_params.uuid, BT_UUID_MTU_TEST_NOTIFY)) {
memcpy(&uuid, BT_UUID_GATT_CCC, sizeof(uuid));
discover_params.uuid = &uuid.uuid;
discover_params.start_handle = attr->handle + 2;
discover_params.type = BT_GATT_DISCOVER_DESCRIPTOR;
subscribe_params.value_handle = bt_gatt_attr_value_handle(attr);
err = bt_gatt_discover(conn, &discover_params);
if (err) {
printk("Discover failed (err %d)\n", err);
}
} else {
subscribe_params.notify = notify_func;
subscribe_params.value = BT_GATT_CCC_NOTIFY;
subscribe_params.ccc_handle = attr->handle;
err = bt_gatt_subscribe(conn, &subscribe_params);
if (err && err != -EALREADY) {
printk("Subscribe failed (err %d)\n", err);
} else {
printk("[SUBSCRIBED]\n");
}
return BT_GATT_ITER_STOP;
}
return BT_GATT_ITER_STOP;
}
static void device_found(const bt_addr_le_t *addr, int8_t rssi, uint8_t type,
struct net_buf_simple *ad)
{
char addr_str[BT_ADDR_LE_STR_LEN];
int err;
if (default_conn) {
return;
}
/* We're only interested in connectable events */
if (type != BT_GAP_ADV_TYPE_ADV_IND &&
type != BT_GAP_ADV_TYPE_ADV_DIRECT_IND) {
return;
}
bt_addr_le_to_str(addr, addr_str, sizeof(addr_str));
printk("Device found: %s (RSSI %d)\n", addr_str, rssi);
/* connect only to devices in close proximity */
if (rssi < -40) {
return;
}
if (bt_le_scan_stop()) {
return;
}
err = bt_conn_le_create(addr, BT_CONN_LE_CREATE_CONN,
BT_LE_CONN_PARAM_DEFAULT, &default_conn);
if (err) {
printk("Create conn to %s failed (%u)\n", addr_str, err);
start_scan();
}
}
static void start_scan(void)
{
int err;
err = bt_le_scan_start(BT_LE_SCAN_PASSIVE, device_found);
if (err) {
printk("Scanning failed to start (err %d)\n", err);
return;
}
printk("Scanning successfully started\n");
}
static void mtu_exchange_cb(struct bt_conn *conn, uint8_t err,
struct bt_gatt_exchange_params *params)
{
printk("%s: MTU exchange %s (%u)\n", __func__,
err == 0U ? "successful" : "failed",
bt_gatt_get_mtu(conn));
}
static struct bt_gatt_exchange_params mtu_exchange_params = {
.func = mtu_exchange_cb
};
static int mtu_exchange(struct bt_conn *conn)
{
int err;
printk("%s: Current MTU = %u\n", __func__, bt_gatt_get_mtu(conn));
printk("%s: Exchange MTU...\n", __func__);
err = bt_gatt_exchange_mtu(conn, &mtu_exchange_params);
if (err) {
printk("%s: MTU exchange failed (err %d)", __func__, err);
}
return err;
}
static void connected(struct bt_conn *conn, uint8_t err)
{
char addr[BT_ADDR_LE_STR_LEN];
bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr));
if (err) {
printk("Failed to connect to %s (%u)\n", addr, err);
bt_conn_unref(default_conn);
default_conn = NULL;
start_scan();
return;
}
printk("Connected: %s\n", addr);
(void)mtu_exchange(conn);
if (conn == default_conn) {
memcpy(&uuid, BT_UUID_MTU_TEST, sizeof(uuid));
discover_params.uuid = &uuid.uuid;
discover_params.func = discover_func;
discover_params.start_handle = BT_ATT_FIRST_ATTRIBUTE_HANDLE;
discover_params.end_handle = BT_ATT_LAST_ATTRIBUTE_HANDLE;
discover_params.type = BT_GATT_DISCOVER_PRIMARY;
err = bt_gatt_discover(default_conn, &discover_params);
if (err) {
printk("Discover failed(err %d)\n", err);
return;
}
}
}
static void disconnected(struct bt_conn *conn, uint8_t reason)
{
char addr[BT_ADDR_LE_STR_LEN];
if (conn != default_conn) {
return;
}
bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr));
printk("Disconnected: %s (reason 0x%02x)\n", addr, reason);
bt_conn_unref(default_conn);
default_conn = NULL;
start_scan();
}
BT_CONN_CB_DEFINE(conn_callbacks) = {
.connected = connected,
.disconnected = disconnected,
};
void run_central_sample(bt_gatt_notify_func_t cb)
{
int err;
notify_cb = cb;
err = bt_enable(NULL);
if (err) {
printk("Bluetooth init failed (err %d)\n", err);
return;
}
printk("Bluetooth initialized\n");
start_scan();
}

View file

@ -0,0 +1,18 @@
/* main.c - Application main entry point */
/*
* Copyright (c) 2023 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/bluetooth/gatt.h>
#include <stddef.h>
#include <stdint.h>
extern void run_central_sample(bt_gatt_notify_func_t cb);
void main(void)
{
run_central_sample(NULL);
}

View file

@ -0,0 +1,13 @@
# SPDX-License-Identifier: Apache-2.0
cmake_minimum_required(VERSION 3.20.0)
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
project(mtu_update)
target_sources(app PRIVATE
src/main.c
src/peripheral_mtu_update.c
)
zephyr_library_include_directories(${ZEPHYR_BASE}/samples/bluetooth)

View file

@ -0,0 +1,13 @@
CONFIG_BT=y
CONFIG_BT_PERIPHERAL=y
CONFIG_BT_DEVICE_NAME="Zephyr Peripheral MTU Update Sample"
CONFIG_LOG=y
CONFIG_BT_L2CAP_LOG_LEVEL_DBG=y
# HCI ACL buffers size
# BT_L2CAP_RX_MTU = CONFIG_BT_BUF_ACL_RX_SIZE - BT_L2CAP_HDR_SIZE
CONFIG_BT_BUF_ACL_RX_SIZE=251
# L2CAP SDU/PDU TX MTU
CONFIG_BT_L2CAP_TX_MTU=247

View file

@ -0,0 +1,9 @@
sample:
name: Bluetooth Peripheral MTU Update
tests:
sample.bluetooth.peripheral_mtu_update:
harness: bluetooth
platform_allow: qemu_cortex_m3 qemu_x86 nrf52_bsim
tags: bluetooth
integration_platforms:
- qemu_cortex_m3

View file

@ -0,0 +1,22 @@
/* main.c - Application main entry point */
/*
* Copyright (c) 2023 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stddef.h>
#include <stdint.h>
extern void run_peripheral_sample(uint8_t *notify_data, size_t notify_data_size, uint16_t seconds);
void main(void)
{
uint8_t notify_data[100] = {};
notify_data[13] = 0x7f;
notify_data[99] = 0x55;
run_peripheral_sample(notify_data, sizeof(notify_data), 0);
}

View file

@ -0,0 +1,73 @@
/*
* Copyright (c) 2023 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/bluetooth/gatt.h>
#include <zephyr/kernel.h>
#include <stddef.h>
#include <stdint.h>
#include <zephyr/sys/printk.h>
#include <zephyr/bluetooth/bluetooth.h>
#include <zephyr/bluetooth/uuid.h>
#include <zephyr/sys/util.h>
#define MTU_TEST_SERVICE_TYPE BT_UUID_128_ENCODE(0x2e2b8dc3, 0x06e0, 0x4f93, 0x9bb2, 0x734091c356f0)
static struct bt_uuid_128 mtu_test_service = BT_UUID_INIT_128(MTU_TEST_SERVICE_TYPE);
static struct bt_uuid_128 notify_characteristic_uuid =
BT_UUID_INIT_128(BT_UUID_128_ENCODE(0x2e2b8dc3, 0x06e0, 0x4f93, 0x9bb2, 0x734091c356f3));
static const struct bt_data adv_ad_data[] = {
BT_DATA_BYTES(BT_DATA_FLAGS, (BT_LE_AD_GENERAL | BT_LE_AD_NO_BREDR)),
BT_DATA_BYTES(BT_DATA_UUID128_ALL, MTU_TEST_SERVICE_TYPE)};
static void ccc_cfg_changed(const struct bt_gatt_attr *attr, uint16_t value)
{
ARG_UNUSED(attr);
bool notif_enabled = (value == BT_GATT_CCC_NOTIFY);
printk("MTU Test Update: notifications %s\n", notif_enabled ? "enabled" : "disabled");
}
BT_GATT_SERVICE_DEFINE(mtu_test, BT_GATT_PRIMARY_SERVICE(&mtu_test_service),
BT_GATT_CHARACTERISTIC(&notify_characteristic_uuid.uuid, BT_GATT_CHRC_NOTIFY,
BT_GATT_PERM_NONE, NULL, NULL, NULL),
BT_GATT_CCC(ccc_cfg_changed, BT_GATT_PERM_READ | BT_GATT_PERM_WRITE));
void mtu_updated(struct bt_conn *conn, uint16_t tx, uint16_t rx)
{
printk("Updated MTU: TX: %d RX: %d bytes\n", tx, rx);
}
static struct bt_gatt_cb gatt_callbacks = {.att_mtu_updated = mtu_updated};
void run_peripheral_sample(uint8_t *notify_data, size_t notify_data_size, uint16_t seconds)
{
int err;
err = bt_enable(NULL);
if (err) {
printk("Bluetooth init failed (err %d)\n", err);
return;
}
bt_gatt_cb_register(&gatt_callbacks);
struct bt_gatt_attr *notify_crch =
bt_gatt_find_by_uuid(mtu_test.attrs, 0xffff, &notify_characteristic_uuid.uuid);
/* Advertise. Auto include name in adv data. Connectable. */
bt_le_adv_start(BT_LE_ADV_CONN_NAME, adv_ad_data, ARRAY_SIZE(adv_ad_data), NULL, 0);
bool infinite = seconds == 0;
for (int i = 0; (i < seconds) || infinite; i++) {
k_sleep(K_SECONDS(1));
bt_gatt_notify(NULL, notify_crch, notify_data, notify_data_size);
}
}