diff --git a/samples/bluetooth/conn_evt_cb/CMakeLists.txt b/samples/bluetooth/conn_evt_cb/CMakeLists.txt new file mode 100644 index 000000000000..b5250b38fd1d --- /dev/null +++ b/samples/bluetooth/conn_evt_cb/CMakeLists.txt @@ -0,0 +1,17 @@ +# +# Copyright (c) 2024 Nordic Semiconductor +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# + +cmake_minimum_required(VERSION 3.20.0) + +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project(NONE) + +zephyr_sources_ifdef(CONFIG_BT_CENTRAL src/central.c) +zephyr_sources_ifdef(CONFIG_BT_PERIPHERAL src/peripheral.c) + +target_sources(app PRIVATE + src/main.c +) diff --git a/samples/bluetooth/conn_evt_cb/README.rst b/samples/bluetooth/conn_evt_cb/README.rst new file mode 100644 index 000000000000..8fa55f1879a4 --- /dev/null +++ b/samples/bluetooth/conn_evt_cb/README.rst @@ -0,0 +1,136 @@ +.. _ble_conn_evt_cb: + +Bluetooth: Connection event callback +##################################### + +.. contents:: + :local: + :depth: 2 + +The Connection event callback sample demonstrates how to use the connection event prepare callback feature described in :ref:`ug_bt_conn_evt_cb`. +It uses the :ref:`latency_readme` and the :ref:`latency_client_readme` to showcase how this feature can be used to minimize the time between data sampling and data transmission. + +Requirements +************ + +The sample supports the following development kits: + +.. table-from-sample-yaml:: + +You can use any two of the development kits mentioned above and mix different development kits. + +Additionally, the sample requires a connection to a computer with a serial terminal for each of the development kits. + +Note that the feature involves triggering a (D)PPI task directly from the SoftDevice Controller link layer, and therefore it is expected that the application and SoftDevice Controller are running on the same core. + +Building and running +******************** +.. |sample path| replace:: :file:`samples/bluetooth/llpm` + +.. include:: /includes/build_and_run.txt + +Testing +======= + +After programming the sample to both development kits, test it by performing the following steps: + +1. Connect to both kits with a terminal emulator (for example, `nRF Connect Serial Terminal`_). + See :ref:`test_and_optimize` for the required settings and steps. +#. Reset both kits. +#. In one of the terminal emulators, type "c" to start the application on the connected board in the central role. +#. In the other terminal emulator, type "p" to start the application in the peripheral role. +#. Observe that latency measurements are printed in the terminals. + +Sample output +============= + +The result should look similar to the following output. + +- For the central:: + + Starting connection event callback sample. + I: SoftDevice Controller build revision: + I: d6 da c7 ae 08 db 72 6f |......ro + I: 2a a3 26 49 2a 4d a8 b3 |*.&I*M.. + I: 98 0e 07 7f |.... + I: HW Platform: Nordic Semiconductor (0x0002) + I: HW Variant: nRF52x (0x0002) + I: Firmware: Standard Bluetooth controller (0x00) Version 214.51162 Build 1926957230 + I: Identity: FA:BB:79:57:D6:45 (random) + I: HCI: version 5.4 (0x0d) revision 0x11fb, manufacturer 0x0059 + I: LMP: version 5.4 (0x0d) subver 0x11fb + Choose device role - type c (central) or p (peripheral): + Central. Starting scanning + Scanning started + Device found: CF:99:32:A5:4B:11 (random) (RSSI -43) + Connected: CF:99:32:A5:4B:11 (random) + Service discovery completed + Latency: 1116 us, round trip latency: 51116 us + Latency: 1116 us, round trip latency: 51116 us + Latency: 1116 us, round trip latency: 51116 us + Latency: 1116 us, round trip latency: 51116 us + Latency: 1116 us, round trip latency: 51116 us + Latency: 1116 us, round trip latency: 51116 us + Latency: 1116 us, round trip latency: 51116 us + Latency: 1116 us, round trip latency: 51116 us + Latency: 1116 us, round trip latency: 51116 us + +- For the peripheral:: + + Starting connection event callback sample. + I: SoftDevice Controller build revision: + I: d6 da c7 ae 08 db 72 6f |......ro + I: 2a a3 26 49 2a 4d a8 b3 |*.&I*M.. + I: 98 0e 07 7f |.... + I: HW Platform: Nordic Semiconductor (0x0002) + I: HW Variant: nRF52x (0x0002) + I: Firmware: Standard Bluetooth controller (0x00) Version 214.51162 Build 1926957230 + I: Identity: CF:99:32:A5:4B:11 (random) + I: HCI: version 5.4 (0x0d) revision 0x11fb, manufacturer 0x0059 + I: LMP: version 5.4 (0x0d) subver 0x11fb + Choose device role - type c (central) or p (peripheral): + Peripheral. Starting advertising + Advertising started + Connected: FA:BB:79:57:D6:45 (random) + Service discovery completed + Latency: 1666 us, round trip latency: 51666 us + Latency: 1086 us, round trip latency: 51086 us + Latency: 1116 us, round trip latency: 51116 us + Latency: 1116 us, round trip latency: 51116 us + Latency: 1086 us, round trip latency: 51086 us + Latency: 1666 us, round trip latency: 51666 us + Latency: 1086 us, round trip latency: 51086 us + Latency: 1116 us, round trip latency: 51116 us + Latency: 1116 us, round trip latency: 51116 us + Latency: 1116 us, round trip latency: 51116 us + Latency: 1666 us, round trip latency: 51666 us + +Dependencies +************* + +This sample uses the following |NCS| libraries: + +* :ref:`latency_readme` +* :ref:`latency_client_readme` + +This sample uses the following `sdk-nrfxlib`_ libraries: + +* :ref:`nrfxlib:softdevice_controller` + +In addition, it uses the following Zephyr libraries: + +* :file:`include/console.h` +* :ref:`zephyr:kernel_api`: + + * :file:`include/kernel.h` + +* :file:`include/sys/printk.h` +* :file:`include/zephyr/types.h` +* :ref:`zephyr:bluetooth_api`: + + * :file:`include/bluetooth/bluetooth.h` + * :file:`include/bluetooth/conn.h` + * :file:`include/bluetooth/gatt.h` + * :file:`include/bluetooth/scan.h` + * :file:`include/bluetooth/gatt_dm.h` + * :file:`include/bluetooth/conn_evt_prepare_cb.h` diff --git a/samples/bluetooth/conn_evt_cb/prj.conf b/samples/bluetooth/conn_evt_cb/prj.conf new file mode 100644 index 000000000000..a597b82bb3a2 --- /dev/null +++ b/samples/bluetooth/conn_evt_cb/prj.conf @@ -0,0 +1,25 @@ +# +# Copyright (c) 2024 Nordic Semiconductor +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# + +CONFIG_NCS_SAMPLES_DEFAULTS=y + +CONFIG_BT_DEVICE_NAME="Conn evt cb" +CONFIG_BT=y +CONFIG_BT_CONN_EVT_CB=y +CONFIG_BT_PERIPHERAL=y +CONFIG_BT_CENTRAL=y +CONFIG_BT_MAX_CONN=2 +CONFIG_BT_CTLR_SDC_PERIPHERAL_COUNT=1 +CONFIG_BT_LATENCY=y +CONFIG_BT_LATENCY_CLIENT=y +CONFIG_BT_GATT_DM=y + +CONFIG_CONSOLE=y +CONFIG_CONSOLE_SUBSYS=y +CONFIG_CONSOLE_HANDLER=y +CONFIG_CONSOLE_GETCHAR=y + +CONFIG_HEAP_MEM_POOL_SIZE=2048 diff --git a/samples/bluetooth/conn_evt_cb/sample.yaml b/samples/bluetooth/conn_evt_cb/sample.yaml new file mode 100644 index 000000000000..f0bbde9be078 --- /dev/null +++ b/samples/bluetooth/conn_evt_cb/sample.yaml @@ -0,0 +1,13 @@ +sample: + description: Connection event callback sample + name: Connection event callback +tests: + sample.bluetooth.conn_evt_cb: + sysbuild: true + build_only: true + integration_platforms: + - nrf52dk/nrf52832 + - nrf52840dk/nrf52840 + - nrf5340dk/nrf5340/cpunet + platform_allow: nrf52dk/nrf52832 nrf52840dk/nrf52840 nrf5340dk/nrf5340/cpunet + tags: bluetooth ci_build sysbuild diff --git a/samples/bluetooth/conn_evt_cb/src/central.c b/samples/bluetooth/conn_evt_cb/src/central.c new file mode 100644 index 000000000000..5023c9c49c36 --- /dev/null +++ b/samples/bluetooth/conn_evt_cb/src/central.c @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include +#include + +#define ADV_NAME_STR_MAX_LEN (sizeof(CONFIG_BT_DEVICE_NAME)) + +void scan_start(void); + +static bool adv_data_parse_cb(struct bt_data *data, void *user_data) +{ + char *name = user_data; + uint8_t len; + + switch (data->type) { + case BT_DATA_NAME_SHORTENED: + case BT_DATA_NAME_COMPLETE: + len = MIN(data->data_len, ADV_NAME_STR_MAX_LEN - 1); + memcpy(name, data->data, len); + name[len] = '\0'; + return false; + default: + return true; + } +} + +static void device_found(const bt_addr_le_t *addr, int8_t rssi, uint8_t type, + struct net_buf_simple *ad) +{ + char name_str[ADV_NAME_STR_MAX_LEN] = {0}; + char addr_str[BT_ADDR_LE_STR_LEN]; + int err; + + /* 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_data_parse(ad, adv_data_parse_cb, name_str); + + if (strncmp(name_str, CONFIG_BT_DEVICE_NAME, ADV_NAME_STR_MAX_LEN) != 0) { + return; + } + + bt_addr_le_to_str(addr, addr_str, sizeof(addr_str)); + printk("Device found: %s (RSSI %d)\n", addr_str, rssi); + + if (bt_le_scan_stop()) { + return; + } + + struct bt_conn *unused_conn; + + err = bt_conn_le_create(addr, BT_CONN_LE_CREATE_CONN, + BT_LE_CONN_PARAM_DEFAULT, &unused_conn); + if (err) { + printk("Create conn to %s failed (%d)\n", addr_str, err); + scan_start(); + } +} + +void scan_start(void) +{ + int err; + + err = bt_le_scan_start(BT_LE_SCAN_ACTIVE, device_found); + if (err) { + printk("Scanning failed to start (err %d)\n", err); + return; + } + + printk("Scanning started\n"); +} diff --git a/samples/bluetooth/conn_evt_cb/src/main.c b/samples/bluetooth/conn_evt_cb/src/main.c new file mode 100644 index 000000000000..0aee560a7e5e --- /dev/null +++ b/samples/bluetooth/conn_evt_cb/src/main.c @@ -0,0 +1,228 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#define CONN_EVT_CB_PREPARE_DISTANCE_US 1000 + +static struct bt_latency latency; +static struct bt_latency_client latency_client; + +void scan_start(void); +void adv_start(void); + +static bool latency_request_time_in_use; +static uint32_t latency_request_time; +static uint32_t conn_interval_us; +static char role; + +static void conn_evt_prepare_cb(struct bt_conn *conn, void *user_data) +{ + int err; + + ARG_UNUSED(conn); + ARG_UNUSED(user_data); + + if (latency_request_time_in_use) { + return; + } + + latency_request_time = k_cycle_get_32(); + err = bt_latency_request(&latency_client, + &latency_request_time, + sizeof(latency_request_time)); + if (err) { + printk("Latency request failed (err %d)\n", err); + } + latency_request_time_in_use = true; +} + +static struct bt_conn_evt_cb callbacks = { + .prepare = conn_evt_prepare_cb, +}; + +static void discovery_complete(struct bt_gatt_dm *dm, void *context) +{ + struct bt_latency_client *latency = context; + + printk("Service discovery completed\n"); + + bt_latency_handles_assign(dm, latency); + bt_gatt_dm_data_release(dm); + + int err = bt_conn_evt_cb_register(bt_gatt_dm_conn_get(dm), + callbacks, + NULL, + CONN_EVT_CB_PREPARE_DISTANCE_US); + if (err) { + printk("Failed enabling notification callback\n"); + } +} + +static void discovery_service_not_found(struct bt_conn *conn, void *context) +{ + printk("Service not found\n"); +} + +static void discovery_error(struct bt_conn *conn, int err, void *context) +{ + printk("Error while discovering GATT database: (err %d)\n", err); +} + +struct bt_gatt_dm_cb discovery_cb = { + .completed = discovery_complete, + .service_not_found = discovery_service_not_found, + .error_found = discovery_error, +}; + +static void connected(struct bt_conn *conn, uint8_t conn_err) +{ + char addr[BT_ADDR_LE_STR_LEN]; + struct bt_conn_info info; + + bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr)); + + if (conn_err) { + printk("Failed to connect to %s (%u)\n", addr, conn_err); + if (role == 'c') { + scan_start(); + } else { + adv_start(); + } + return; + } + + printk("Connected: %s\n", addr); + + if (bt_conn_get_info(conn, &info)) { + printk("Failed getting conn info\n"); + return; + } + + conn_interval_us = BT_CONN_INTERVAL_TO_US(info.le.interval); + + /*Start service discovery when link is encrypted*/ + int err = bt_gatt_dm_start(conn, BT_UUID_LATENCY, &discovery_cb, + &latency_client); + if (err) { + printk("Discover failed (err %d)\n", err); + } +} + +static void disconnected(struct bt_conn *conn, uint8_t reason) +{ + char addr[BT_ADDR_LE_STR_LEN]; + + bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr)); + + printk("Disconnected: %s (reason %u)\n", addr, reason); + + if (role == 'c') { + scan_start(); + } else { + adv_start(); + } +} + +static void on_conn_param_updated(struct bt_conn *conn, + uint16_t interval, + uint16_t latency, + uint16_t timeout) +{ + ARG_UNUSED(conn); + ARG_UNUSED(latency); + ARG_UNUSED(timeout); + + conn_interval_us = BT_CONN_INTERVAL_TO_US(interval); +} + +BT_CONN_CB_DEFINE(conn_callbacks) = { + .connected = connected, + .disconnected = disconnected, + .le_param_updated = on_conn_param_updated, +}; + +static void latency_response_handler(const void *buf, uint16_t len) +{ + uint32_t latency_time; + + if (len == sizeof(latency_time)) { + /* compute how long the time spent */ + + latency_time = *((uint32_t *)buf); + uint32_t cycles_spent = k_cycle_get_32() - latency_time; + uint32_t total_latency_us = (uint32_t)k_cyc_to_us_floor64(cycles_spent); + + /* The latency service uses ATT Write Requests. + * The ATT Write Response is sent in the next connection event. + * The actual expected latency is therefore the total latency minus the connection + * interval. + */ + uint32_t latency_minus_conn_interval = total_latency_us - conn_interval_us; + + printk("Latency: %d us, round trip latency: %d us\n", + latency_minus_conn_interval, total_latency_us); + + latency_request_time_in_use = false; + } +} + +static const struct bt_latency_client_cb latency_client_cb = { + .latency_response = latency_response_handler +}; + +int main(void) +{ + int err; + + printk("Starting connection event callback sample.\n"); + + err = bt_enable(NULL); + if (err) { + printk("Bluetooth init failed (err %d)\n", err); + return 0; + } + + err = bt_latency_init(&latency, NULL); + if (err) { + printk("Latency service initialization failed (err %d)\n", err); + return 0; + } + + err = bt_latency_client_init(&latency_client, &latency_client_cb); + if (err) { + printk("Latency client initialization failed (err %d)\n", err); + return 0; + } + + console_init(); + do { + printk("Choose device role - type c (central) or p (peripheral): "); + + role = console_getchar(); + + switch (role) { + case 'p': + printk("\nPeripheral. Starting advertising\n"); + adv_start(); + break; + case 'c': + printk("\nCentral. Starting scanning\n"); + scan_start(); + break; + default: + printk("\n"); + break; + } + } while (role != 'c' && role != 'p'); +} diff --git a/samples/bluetooth/conn_evt_cb/src/peripheral.c b/samples/bluetooth/conn_evt_cb/src/peripheral.c new file mode 100644 index 000000000000..a4f4bc91c7ad --- /dev/null +++ b/samples/bluetooth/conn_evt_cb/src/peripheral.c @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include +#include + +static const struct bt_data ad[] = { + BT_DATA_BYTES(BT_DATA_FLAGS, (BT_LE_AD_GENERAL | BT_LE_AD_NO_BREDR)), + BT_DATA(BT_DATA_NAME_COMPLETE, CONFIG_BT_DEVICE_NAME, sizeof(CONFIG_BT_DEVICE_NAME) - 1), +}; + +void adv_start(void) +{ + int err; + + err = bt_le_adv_start(BT_LE_ADV_CONN_ONE_TIME, ad, ARRAY_SIZE(ad), NULL, 0); + if (err) { + printk("Advertising failed to start (err %d)\n", err); + return; + } + + printk("Advertising started\n"); +} diff --git a/samples/bluetooth/conn_evt_cb/sysbuild.cmake b/samples/bluetooth/conn_evt_cb/sysbuild.cmake new file mode 100644 index 000000000000..ccc4f6e44224 --- /dev/null +++ b/samples/bluetooth/conn_evt_cb/sysbuild.cmake @@ -0,0 +1,13 @@ +# +# Copyright (c) 2024 Nordic Semiconductor +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# + +if (${SB_CONFIG_BOARD_NRF5340DK_NRF5340_CPUNET}) + ExternalZephyrProject_Add( + APPLICATION empty_app_core + SOURCE_DIR ${ZEPHYR_NRF_MODULE_DIR}/samples/nrf5340/empty_app_core + BOARD nrf5340dk/nrf5340/cpuapp + ) +endif() diff --git a/samples/bluetooth/conn_evt_cb/sysbuild.conf b/samples/bluetooth/conn_evt_cb/sysbuild.conf new file mode 100644 index 000000000000..6408669a8474 --- /dev/null +++ b/samples/bluetooth/conn_evt_cb/sysbuild.conf @@ -0,0 +1 @@ +SB_CONFIG_PARTITION_MANAGER=n