diff --git a/samples/bluetooth/scanning_while_connecting/CMakeLists.txt b/samples/bluetooth/scanning_while_connecting/CMakeLists.txt new file mode 100644 index 000000000000..6a643e31fd02 --- /dev/null +++ b/samples/bluetooth/scanning_while_connecting/CMakeLists.txt @@ -0,0 +1,11 @@ +# +# 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(connection_establishment_benchmark) + +target_sources(app PRIVATE src/main.c) diff --git a/samples/bluetooth/scanning_while_connecting/Kconfig.sysbuild b/samples/bluetooth/scanning_while_connecting/Kconfig.sysbuild new file mode 100644 index 000000000000..11bb1e685162 --- /dev/null +++ b/samples/bluetooth/scanning_while_connecting/Kconfig.sysbuild @@ -0,0 +1,10 @@ +# +# Copyright (c) 2024 Nordic Semiconductor +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# + +source "${ZEPHYR_BASE}/share/sysbuild/Kconfig" + +config NRF_DEFAULT_IPC_RADIO + default y diff --git a/samples/bluetooth/scanning_while_connecting/README.rst b/samples/bluetooth/scanning_while_connecting/README.rst new file mode 100644 index 000000000000..47c1bcbb1e68 --- /dev/null +++ b/samples/bluetooth/scanning_while_connecting/README.rst @@ -0,0 +1,228 @@ +.. _bt_scanning_while_connecting: + +Bluetooth: Scanning while connecting +#################################### + +.. contents:: + :local: + :depth: 2 + +The sample demonstrates how to reduce the time to establish connections to many devices, typically done when provisioning devices to a network. +The total connection establishment time is reduced by scanning while connecting and by using the filter accept list. + +Requirements +************ + +The sample supports the following development kits: + +.. table-from-sample-yaml:: + +The sample also requires at least one other development kit. +Out of the box, this sample can be used together with the :ref:`peripheral_with_multiple_identities`. +This sample filters out devices with a different name than a device running the :ref:`peripheral_with_multiple_identities` sample. +The :ref:`peripheral_with_multiple_identities` sample acts as multiple peripheral devices. + +Overview +******** + +You can use this sample as a starting point to implement an application designed to provision a network of Bluetooth peripherals. +The approaches demonstrated in this sample will reduce the total time to connect to many devices. +A typical use-case is a gateway in a network of devices using Periodic Advertising with Responses. + +To illustrate how connection establishment speed can be improved, it measures the time needed to connect to 16 devices with three different modes: sequential scanning and connection establishment, concurrent scanning while connecting, and concurrent scanning while connecting with the filter accept list. + +Sequential scanning and connection establishment +================================================ + +This is the slowest and simplest approach. +Sequential scanning and connection establishment is recommended when the application only needs to connect a handful of devices. + +The message sequence chart below illustrates the sequence of events. +After a device is discovered, the application stops scanning and attempts to connect to the device. +Once the connection is established, the application starts the scanner again to discover other connectable devices. + +.. msc:: + hscale = "1.3"; + App, Stack, Peers; + App=>Stack [label="scan_start()"]; + Peers=>Stack [label="ADV_IND(A)"]; + Stack=>App [label="scan_recv(A)"]; + App=>Stack [label="scan_stop()"]; + App=>Stack [label="bt_conn_le_create(A)"]; + Peers=>Stack [label="ADV_IND(A)"]; + Stack=>Peers [label="CONNECT_IND(A)"]; + Stack=>App [label="connected_cb(A)"]; + App=>Stack [label="scan_start()"]; + Peers=>Stack [label="ADV_IND(B)"]; + Stack=>App [label="scan_recv(B)"]; + App=>Stack [label="scan_stop()"]; + App=>Stack [label="bt_conn_le_create(B)"]; + Peers=>Stack [label="ADV_IND(B)"]; + Stack=>Peers [label="CONNECT_IND(B)"]; + Stack=>App [label="connected_cb(B)"]; + +Concurrent scanning while connecting +==================================== + +This mode requires the application to enable the :kconfig:option:`CONFIG_BT_SCAN_AND_INITIATE_IN_PARALLEL` Kconfig option. +In this mode, the scanner is not stopped when the application creates connections. +During a connection establishment procedure to a device, the application caches other devices it also wants to connect to. +Once the connection establishment procedure is complete, it can immediately initiate a new connection establishment procedure to the cached device. +When connecting to a cached device, the connection establishment procedure takes one less advertising interval compared to the sequential scanning and connection establishment mode. + +.. msc:: + hscale = "1.3"; + App, Stack, Peers; + App=>Stack [label="scan_start()"]; + Peers=>Stack [label="ADV_IND(A)"]; + Stack=>App [label="scan_recv(A)"]; + App=>Stack [label="bt_conn_le_create(A)"]; + Peers=>Stack [label="ADV_IND(B)"]; + App rbox App [label="Cache address B"]; + Peers=>Stack [label="ADV_IND(A)"]; + Stack=>Peers [label="CONNECT_IND(A)"]; + Stack=>App [label="connected_cb(A)"]; + App=>Stack [label="bt_conn_le_create(B)"]; + Peers=>Stack [label="ADV_IND(B)"]; + Stack=>Peers [label="CONNECT_IND(B)"]; + Stack=>App [label="connected_cb(B)"]; + +Concurrent scanning while connecting with the filter accept list +================================================================ + +This mode requires the application to enable the :kconfig:option:`CONFIG_BT_FILTER_ACCEPT_LIST` Kconfig option in addition to :kconfig:option:`CONFIG_BT_SCAN_AND_INITIATE_IN_PARALLEL`. +When the application starts the connection establishment procedure with the filter accept list, it can connect to any of the previously cached devices. +This reduces the total connection setup time even more, because connection establishment is not relying on the on-air presence of only one of the cached devices. + +.. msc:: + hscale = "1.3"; + App, Stack, Peers; + App=>Stack [label="scan_start()"]; + Peers=>Stack [label="ADV_IND(A)"]; + Stack=>App [label="scan_recv(A)"]; + App=>Stack [label="bt_conn_le_create(A)"]; + Peers=>Stack [label="ADV_IND(B)"]; + Peers=>Stack [label="ADV_IND(C)"]; + Peers=>Stack [label="ADV_IND(D)"]; + App rbox App [label="Cache addresses B, C, D"]; + Peers=>Stack [label="ADV_IND(A)"]; + Stack=>Peers [label="CONNECT_IND(A)"]; + Stack=>App [label="connected_cb(A)"]; + App rbox App [label="Set filter accept list to\nB, C, D"]; + App=>Stack [label="bt_conn_le_create_auto()"]; + Stack rbox Stack [label="The stack will connect to the first present ADV_IND of\nthe peers B,C,D"]; + Peers=>Stack [label="ADV_IND(C)"]; + Stack=>Peers [label="CONNECT_IND(C)"]; + Stack=>App [label="connected_cb(C)"]; + +.. note:: + This sample application assumes it will never have to cache more devices than the maximum number of addresses that can be stored in the filter accept list. + For applications that cannot adhere to this simplification, the function :cfunc:`cache_peer_address` can be changed to not store more than defined by the :kconfig:option:`CONFIG_BT_CTLR_FAL_SIZE` Kconfig option. + Another simplification done in the sample application is storing duplicate devices in the filter accept list. + +Configuration +************* + +|config| + +Building and running +******************** + +.. |sample path| replace:: :file:`samples/bluetooth/scanning_while_connecting` + +.. include:: /includes/build_and_run.txt + +Testing +======= + +|test_sample| + +1. |connect_kit| +#. |connect_terminal| +#. Observe that the sample connects and prints out how much time it takes to connect to all peripherals. + +Sample output +============= + +The result should look similar to the following output:: + + *** Booting nRF Connect SDK v2.8.99-1c63490f0539 *** + *** Using Zephyr OS v3.7.99-b9bc0846b926 *** + I: SoftDevice Controller build revision: + I: 49 40 e2 c0 6b e5 0d b3 |I@..k... + I: ba a6 48 5e 49 a6 95 3d |..H^I..= + I: 65 35 b6 7c |e5.| + I: HW Platform: Nordic Semiconductor (0x0002) + I: HW Variant: nRF54Lx (0x0005) + I: Firmware: Standard Bluetooth controller (0x00) Version 73.57920 Build 233139136 + I: Identity: F6:BF:24:7D:46:5D (random) + I: HCI: version 6.0 (0x0e) revision 0x3030, manufacturer 0x0059 + I: LMP: version 6.0 (0x0e) subver 0x3030 + I: Bluetooth initialized + + I: SEQUENTIAL_SCAN_AND_CONNECT: + I: starting sample benchmark + I: Connected to FF:AB:68:0C:34:FD (random), number of connections 1 + I: Connected to E2:12:BF:D3:FB:D5 (random), number of connections 2 + I: Connected to EE:37:AA:62:A2:FB (random), number of connections 3 + I: Connected to C7:9B:42:1B:48:F8 (random), number of connections 4 + I: Connected to F0:27:5B:37:0F:4B (random), number of connections 5 + I: Connected to C8:BA:1D:6F:95:2B (random), number of connections 6 + I: Connected to DF:02:31:C3:0B:C2 (random), number of connections 7 + I: Connected to C7:E1:60:7A:F1:E0 (random), number of connections 8 + I: Connected to CA:89:50:33:AB:31 (random), number of connections 9 + I: Connected to E9:47:6B:FA:2F:DE (random), number of connections 10 + I: Connected to EF:92:DC:88:3B:B3 (random), number of connections 11 + I: Connected to F4:9C:8C:24:F9:44 (random), number of connections 12 + I: Connected to DD:84:44:64:5D:FB (random), number of connections 13 + I: Connected to FB:92:1D:8E:8C:D8 (random), number of connections 14 + I: Connected to D9:E5:51:E0:5E:24 (random), number of connections 15 + I: Connected to CF:2F:99:89:A3:4D (random), number of connections 16 + I: 12 seconds to create 16 connections + I: Disconnecting connections... + I: --------------------------------------------------------------------- + I: --------------------------------------------------------------------- + I: CONCURRENT_SCAN_AND_CONNECT: + I: starting sample benchmark + I: Connected to F0:27:5B:37:0F:4B (random), number of connections 1 + I: Connected to EE:37:AA:62:A2:FB (random), number of connections 2 + I: Connected to D1:3D:B1:AA:84:27 (random), number of connections 3 + I: Connected to CA:89:50:33:AB:31 (random), number of connections 4 + I: Connected to C0:38:F8:47:10:17 (random), number of connections 5 + I: Connected to E9:47:6B:FA:2F:DE (random), number of connections 6 + I: Connected to D9:E5:51:E0:5E:24 (random), number of connections 7 + I: Connected to FB:92:1D:8E:8C:D8 (random), number of connections 8 + I: Connected to C7:E1:60:7A:F1:E0 (random), number of connections 9 + I: Connected to E2:6D:21:28:C7:DB (random), number of connections 10 + I: Connected to DF:02:31:C3:0B:C2 (random), number of connections 11 + I: Connected to F0:F2:2A:C1:F7:72 (random), number of connections 12 + I: Connected to DD:84:44:64:5D:FB (random), number of connections 13 + I: Connected to F4:9C:8C:24:F9:44 (random), number of connections 14 + I: Connected to EF:92:DC:88:3B:B3 (random), number of connections 15 + I: Connected to C8:BA:1D:6F:95:2B (random), number of connections 16 + I: 9 seconds to create 16 connections + I: Disconnecting connections... + I: --------------------------------------------------------------------- + I: --------------------------------------------------------------------- + I: CONCURRENT_SCAN_AND_CONNECT_FILTER_ACCEPT_LIST: + I: starting sample benchmark + I: Connected to DD:84:44:64:5D:FB (random), number of connections 1 + I: Connected to C7:E1:60:7A:F1:E0 (random), number of connections 2 + I: Connected to C7:9B:42:1B:48:F8 (random), number of connections 3 + I: Connected to E9:47:6B:FA:2F:DE (random), number of connections 4 + I: Connected to E2:12:BF:D3:FB:D5 (random), number of connections 5 + I: Connected to FB:92:1D:8E:8C:D8 (random), number of connections 6 + I: Connected to F0:F2:2A:C1:F7:72 (random), number of connections 7 + I: Connected to CA:89:50:33:AB:31 (random), number of connections 8 + I: Connected to F4:9C:8C:24:F9:44 (random), number of connections 9 + I: Connected to E2:6D:21:28:C7:DB (random), number of connections 10 + I: Connected to FF:AB:68:0C:34:FD (random), number of connections 11 + I: Connected to EE:37:AA:62:A2:FB (random), number of connections 12 + I: Connected to D1:3D:B1:AA:84:27 (random), number of connections 13 + I: Connected to CF:2F:99:89:A3:4D (random), number of connections 14 + I: Connected to EF:92:DC:88:3B:B3 (random), number of connections 15 + I: Connected to D9:E5:51:E0:5E:24 (random), number of connections 16 + I: 4 seconds to create 16 connections + I: Disconnecting connections... + I: --------------------------------------------------------------------- + I: --------------------------------------------------------------------- diff --git a/samples/bluetooth/scanning_while_connecting/peripheral/CMakeLists.txt b/samples/bluetooth/scanning_while_connecting/peripheral/CMakeLists.txt index 6c08b84e57b5..a397c2093ff3 100644 --- a/samples/bluetooth/scanning_while_connecting/peripheral/CMakeLists.txt +++ b/samples/bluetooth/scanning_while_connecting/peripheral/CMakeLists.txt @@ -1,5 +1,5 @@ # -# Copyright (c) 2020 Nordic Semiconductor ASA +# Copyright (c) 2024 Nordic Semiconductor ASA # # SPDX-License-Identifier: LicenseRef-Nordic-5-Clause # diff --git a/samples/bluetooth/scanning_while_connecting/peripheral/prj.conf b/samples/bluetooth/scanning_while_connecting/peripheral/prj.conf index f30c0605dd12..a277b90ace08 100644 --- a/samples/bluetooth/scanning_while_connecting/peripheral/prj.conf +++ b/samples/bluetooth/scanning_while_connecting/peripheral/prj.conf @@ -1,5 +1,5 @@ # -# Copyright (c) 2020 Nordic Semiconductor +# Copyright (c) 2024 Nordic Semiconductor # # SPDX-License-Identifier: LicenseRef-Nordic-5-Clause # @@ -10,7 +10,9 @@ CONFIG_BT=y CONFIG_BT_PERIPHERAL=y CONFIG_BT_CTLR_ADV_EXT=y CONFIG_BT_EXT_ADV=y +CONFIG_BT_GAP_AUTO_UPDATE_CONN_PARAMS=n -CONFIG_BT_EXT_ADV_MAX_ADV_SET=2 -CONFIG_BT_MAX_CONN=2 -CONFIG_BT_ID_MAX=2 \ No newline at end of file +CONFIG_BT_EXT_ADV_MAX_ADV_SET=20 +CONFIG_BT_MAX_CONN=20 +CONFIG_BT_ID_MAX=20 +CONFIG_BT_BUF_ACL_RX_COUNT=21 diff --git a/samples/bluetooth/scanning_while_connecting/peripheral/src/main.c b/samples/bluetooth/scanning_while_connecting/peripheral/src/main.c index 0fe6abbc77be..89412c5e927e 100644 --- a/samples/bluetooth/scanning_while_connecting/peripheral/src/main.c +++ b/samples/bluetooth/scanning_while_connecting/peripheral/src/main.c @@ -74,15 +74,19 @@ static void start_connectable_advertiser(struct k_work *work) static int setup_advertiser(uint8_t id_adv) { - struct bt_le_adv_param adv_param = - BT_LE_ADV_PARAM_INIT(BT_LE_ADV_OPT_CONNECTABLE, - BT_GAP_ADV_SLOW_INT_MIN, - BT_GAP_ADV_SLOW_INT_MAX, - NULL); - + struct bt_le_adv_param adv_param = { + .id = BT_ID_DEFAULT, + .sid = 0, + .secondary_max_skip = 0, + .options = BT_LE_ADV_OPT_CONNECTABLE, // | BT_LE_ADV_OPT_EXT_ADV, + .interval_min = 800, /* Minimum Advertising Interval (N * 0.625 milliseconds) */ + .interval_max = 800, /* Maximum Advertising Interval (N * 0.625 milliseconds) */ + .peer = NULL, + }; + size_t id_count = 0xFF; int err; - + bt_id_get(NULL, &id_count); if (id_adv == id_count) { int id; @@ -102,7 +106,7 @@ static int setup_advertiser(uint8_t id_adv) printk("Using current id: %u\n", id_adv); adv_param.id = id_adv; advertisers[id_adv].id = id_adv; - + err = bt_le_ext_adv_create(&adv_param, NULL, &advertisers[id_adv].adv); if (err) { printk("Failed to create advertiser set (err %d)\n", err); @@ -136,7 +140,7 @@ int main(void) } printk("Bluetooth initialized\n"); - + printk("Starting %d advertisers\n", CONFIG_BT_EXT_ADV_MAX_ADV_SET); for (uint8_t i = 0; i < CONFIG_BT_EXT_ADV_MAX_ADV_SET; i++) { diff --git a/samples/bluetooth/scanning_while_connecting/prj.conf b/samples/bluetooth/scanning_while_connecting/prj.conf new file mode 100644 index 000000000000..f2f8f481a5fc --- /dev/null +++ b/samples/bluetooth/scanning_while_connecting/prj.conf @@ -0,0 +1,23 @@ +# +# Copyright (c) 2024 Nordic Semiconductor +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# + +CONFIG_NCS_SAMPLES_DEFAULTS=y + +CONFIG_BT=y +CONFIG_BT_CENTRAL=y +CONFIG_BT_FILTER_ACCEPT_LIST=y +CONFIG_BT_SCAN_AND_INITIATE_IN_PARALLEL=y +CONFIG_BT_CTLR_SDC_ALLOW_PARALLEL_SCANNING_AND_INITIATING=y + +# A ring buffer is used as a device address cache +CONFIG_RING_BUFFER=y + +CONFIG_BT_CTLR_FAL_SIZE=255 + +CONFIG_LOG=y + +CONFIG_BT_MAX_CONN=16 +CONFIG_BT_BUF_ACL_RX_COUNT=17 diff --git a/samples/bluetooth/scanning_while_connecting/sample.yaml b/samples/bluetooth/scanning_while_connecting/sample.yaml new file mode 100644 index 000000000000..44975d5d08e2 --- /dev/null +++ b/samples/bluetooth/scanning_while_connecting/sample.yaml @@ -0,0 +1,16 @@ +sample: + description: Bluetooth Low Energy fast connection establishment sample + name: Bluetooth LE central scanning while connecting +tests: + sample.bluetooth.scanning_while_connecting: + sysbuild: true + build_only: true + integration_platforms: + - nrf52840dk/nrf52840 + - nrf5340dk/nrf5340/cpuapp + - nrf54l15dk/nrf54l15/cpuapp + platform_allow: + - nrf52840dk/nrf52840 + - nrf5340dk/nrf5340/cpuapp + - nrf54l15dk/nrf54l15/cpuapp + tags: bluetooth ci_build sysbuild diff --git a/samples/bluetooth/scanning_while_connecting/src/main.c b/samples/bluetooth/scanning_while_connecting/src/main.c new file mode 100644 index 000000000000..5b602cfdfc89 --- /dev/null +++ b/samples/bluetooth/scanning_while_connecting/src/main.c @@ -0,0 +1,464 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +LOG_MODULE_REGISTER(app); + +typedef enum { + /** The central scans for connectable addresses, then + * disables the scanner before starting to establish a + * connection to a scanned connectable address. + * Once the connection is established, the central must start the + * scanner again to repeat the process to establish more connections. + */ + SEQUENTIAL_SCAN_AND_CONNECT, + + /** The central scans for connectable addresses, and starts + * to establish a connection. While establishing a connection, + * the central can continue to scan and cache other connectable + * addresses. Once the pending connection is established, the + * central can immediately establish a connection to one of the + * cached addresses. + */ + CONCURRENT_SCAN_AND_CONNECT, + + /** The central scans for connectable addresses, and starts + * to establish a connection. While establishing a connection, + * the central can continue to scan and cache other connectable + * addresses. Once the pending connection is established, the + * central can add the cached addresses to the filter accept list, and + * establish a connection using a filter accept list. + */ + CONCURRENT_SCAN_AND_CONNECT_FILTER_ACCEPT_LIST, +} connection_establishment_mode_t; + +static const connection_establishment_mode_t conn_establishment_modes[] = { + SEQUENTIAL_SCAN_AND_CONNECT, + CONCURRENT_SCAN_AND_CONNECT, + CONCURRENT_SCAN_AND_CONNECT_FILTER_ACCEPT_LIST, +}; + +static connection_establishment_mode_t active_conn_establishment_mode; + +K_SEM_DEFINE(all_devices_connected_sem, 0, 1); + +K_SEM_DEFINE(all_devices_disconnected_sem, 0, 1); + +/** Mutex is used to protect from concurrent reads + * and writes in the ring buffer. + */ +K_MUTEX_DEFINE(mutex_ring_buf); + +static const char adv_name[] = "Nordic Peripheral ID"; +#define ADV_NAME_STR_MAX_LEN (sizeof(adv_name)) + +RING_BUF_DECLARE(connectable_peers_ring_buf, CONFIG_BT_MAX_CONN * sizeof(bt_addr_le_t)); + +static void scan_start(void); +static void scan_stop(void); + +static volatile bool connection_establishment_ongoing; + +static volatile uint32_t num_connections; + +/** This is used to store device addresses that can be connected to later. + * + * This function can be changed to use another data structure + * than a ring buffer to store the addresses. + */ +static uint32_t cache_peer_address(const bt_addr_le_t *addr) +{ + k_mutex_lock(&mutex_ring_buf, K_FOREVER); + + uint32_t bytes_written = ring_buf_put(&connectable_peers_ring_buf, + (uint8_t *) addr, sizeof(bt_addr_le_t)); + + k_mutex_unlock(&mutex_ring_buf); + + return bytes_written; +} + +/** This is used to read and remove a device address from the ring buffer. + * + * This function can be changed to use another data structure + * than a ring buffer to read the cached addresses. + */ +static bool read_cached_peer_addr(const bt_addr_le_t *out_addr) +{ + while (true) { + k_mutex_lock(&mutex_ring_buf, K_FOREVER); + + uint32_t bytes_read = ring_buf_get(&connectable_peers_ring_buf, + (uint8_t *) out_addr, sizeof(bt_addr_le_t)); + + k_mutex_unlock(&mutex_ring_buf); + + if (bytes_read > 0) { + struct bt_conn *conn = bt_conn_lookup_addr_le(BT_ID_DEFAULT, out_addr); + + if (conn) { + /* We have already connected to this address. */ + bt_conn_unref(conn); + } else { + return true; + } + } else { + return false; + } + } +} + +static bool add_cached_addresses_to_filter_accept_list(void) +{ + bool addr_added_to_filter_accept_list = false; + + while (true) { + bt_addr_le_t addr; + bool found_address = read_cached_peer_addr(&addr); + + if (!found_address) { + /* We don't have any peer addresses cached. */ + break; + } + + int err = bt_le_filter_accept_list_add(&addr); + + if (err) { + LOG_ERR("bt_le_filter_accept_list_add failed (err %d)", err); + } else { + addr_added_to_filter_accept_list = true; + } + } + + return addr_added_to_filter_accept_list; +} + +static void disconnected(struct bt_conn *conn, uint8_t reason) +{ + char addr_str[BT_ADDR_LE_STR_LEN]; + + bt_addr_le_to_str(bt_conn_get_dst(conn), addr_str, sizeof(addr_str)); + + LOG_DBG("Disconnected from addr %s (reason %u) %s", addr_str, + reason, bt_hci_err_to_str(reason)); + + num_connections--; + if (num_connections == 0) { + k_sem_give(&all_devices_disconnected_sem); + } +} + +static void connected(struct bt_conn *conn, uint8_t err) +{ + connection_establishment_ongoing = false; + char addr_str[BT_ADDR_LE_STR_LEN]; + + bt_addr_le_to_str(bt_conn_get_dst(conn), addr_str, sizeof(addr_str)); + + if (err) { + LOG_WRN("Failed to connect to %s (err %u) %s", addr_str, + err, bt_hci_err_to_str(err)); + } else { + num_connections++; + LOG_INF("Connected to %s, number of connections %u", addr_str, num_connections); + if (num_connections == CONFIG_BT_MAX_CONN) { + /** We have connected to all advertisers. + * Give the semaphore to move on to the + * next round of connecting to peer advertisers. + */ + k_sem_give(&all_devices_connected_sem); + return; + } + } + + struct bt_le_conn_param conn_param = { + .interval_min = 500, /* Minimum Connection Interval (interval_min * 1.25 ms) */ + .interval_max = 500, /* Maximum Connection Interval (interval_max * 1.25 ms) */ + .latency = 0, + .timeout = 400, /* Supervision Timeout (timeout * 10 ms) */ + }; + + /** A connection parameter update with a longer connection interval + * is done to give make more time available to the scanner. + */ + int zephyr_err = bt_conn_le_param_update(conn, &conn_param); + + if (zephyr_err) { + LOG_ERR("bt_conn_le_param_update failed (err %d)", zephyr_err); + } + + if (active_conn_establishment_mode == SEQUENTIAL_SCAN_AND_CONNECT) { + scan_start(); + } +} + +static void try_connect(void) +{ + if (connection_establishment_ongoing) { + return; + } + + connection_establishment_ongoing = true; + + /** Interval and window of the create connection parameters + * must be the same as the interval and window of the scanner + * parameters to enable scanning and connecting concurrently. + */ + const struct bt_conn_le_create_param create_param = { + .options = BT_CONN_LE_OPT_NONE, + .interval = BT_GAP_SCAN_FAST_INTERVAL_MIN, + .window = BT_GAP_SCAN_FAST_INTERVAL_MIN, + .interval_coded = 0, + .window_coded = 0, + .timeout = 0, + }; + + int err; + char addr_str[BT_ADDR_LE_STR_LEN]; + + if ((active_conn_establishment_mode == SEQUENTIAL_SCAN_AND_CONNECT) || + (active_conn_establishment_mode == CONCURRENT_SCAN_AND_CONNECT)) { + + bt_addr_le_t addr; + bool found_addr = read_cached_peer_addr(&addr); + + if (found_addr) { + if (active_conn_establishment_mode == SEQUENTIAL_SCAN_AND_CONNECT) { + scan_stop(); + } + + bt_addr_le_to_str(&addr, addr_str, sizeof(addr_str)); + + struct bt_conn *unused_conn = NULL; + + LOG_DBG("Connecting to %s", addr_str); + + err = bt_conn_le_create(&addr, &create_param, BT_LE_CONN_PARAM_DEFAULT, + &unused_conn); + + if (unused_conn) { + bt_conn_unref(unused_conn); + } + + if (err) { + + connection_establishment_ongoing = false; + LOG_ERR("bt_conn_le_create failed (err %d)", err); + + if (active_conn_establishment_mode == SEQUENTIAL_SCAN_AND_CONNECT) { + scan_start(); + } + } + } + } else { + + bool addr_added_to_filter_accept_list + = add_cached_addresses_to_filter_accept_list(); + + if (addr_added_to_filter_accept_list) { + + LOG_DBG("Connecting using filter accept list"); + + err = bt_conn_le_create_auto(&create_param, BT_LE_CONN_PARAM_DEFAULT); + if (err) { + connection_establishment_ongoing = false; + LOG_ERR("bt_conn_le_create_auto failed (err %d)", err); + } + + } else { + connection_establishment_ongoing = false; + } + } +} + +static struct bt_conn_cb conn_callbacks = { + .connected = connected, + .disconnected = disconnected, +}; + +static bool adv_data_parse_cb(struct bt_data *data, void *user_data) +{ + char *rcvd_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(rcvd_name, data->data, len); + rcvd_name[len] = '\0'; + return false; + default: + return true; + } +} + +static void scan_recv(const struct bt_le_scan_recv_info *info, struct net_buf_simple *buf) +{ + /** We're only interested in connectable advertisers to + * show faster connection establishment + */ + if (info->adv_type != BT_GAP_ADV_TYPE_ADV_IND && + info->adv_type != BT_GAP_ADV_TYPE_EXT_ADV) { + return; + } + + /* connect only to devices in close proximity */ + if (info->rssi < -50) { + return; + } + + char name_str[ADV_NAME_STR_MAX_LEN] = {0}; + + bt_data_parse(buf, adv_data_parse_cb, name_str); + + if (strncmp(name_str, adv_name, ADV_NAME_STR_MAX_LEN) == 0) { + + char addr_str[BT_ADDR_LE_STR_LEN] = {0}; + + bt_addr_le_to_str(info->addr, addr_str, sizeof(addr_str)); + + uint32_t bytes_written = cache_peer_address(info->addr); + + if (bytes_written > 0) { + LOG_DBG("Scanned and cached connectable addr %s", addr_str); + } + + try_connect(); + } +} + +static struct bt_le_scan_cb scan_callbacks = { + .recv = scan_recv, +}; + +static void scan_start(void) +{ + int err = bt_le_scan_start(BT_LE_SCAN_PASSIVE_CONTINUOUS, NULL); + + if (err) { + LOG_ERR("Scanning failed to start (err %d)", err); + } + LOG_DBG("Started scanning"); +} + +static void scan_stop(void) +{ + int err = bt_le_scan_stop(); + + if (err) { + LOG_ERR("Failed to stop scanning (err %d)", err); + } + LOG_DBG("Stopped scanning"); + LOG_INF("Stopped scanning"); +} + +static void disconnect(struct bt_conn *conn, void *data) +{ + char addr[BT_ADDR_LE_STR_LEN]; + int err; + + bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr)); + + err = bt_conn_disconnect(conn, BT_HCI_ERR_REMOTE_USER_TERM_CONN); + + if (err) { + LOG_ERR("Failed disconnection %s", addr); + } +} + +static void print_conn_establishment_mode( + connection_establishment_mode_t active_conn_establishment_mode) +{ + switch (active_conn_establishment_mode) { + case SEQUENTIAL_SCAN_AND_CONNECT: + LOG_INF("SEQUENTIAL_SCAN_AND_CONNECT: "); + break; + case CONCURRENT_SCAN_AND_CONNECT: + LOG_INF("CONCURRENT_SCAN_AND_CONNECT: "); + break; + case CONCURRENT_SCAN_AND_CONNECT_FILTER_ACCEPT_LIST: + LOG_INF("CONCURRENT_SCAN_AND_CONNECT_FILTER_ACCEPT_LIST: "); + break; + default: + break; + } +} + +int main(void) +{ + int err; + + int64_t uptime_start_scan_ms; + int64_t total_uptime_create_all_connections_ms; + + err = bt_enable(NULL); + + if (err) { + LOG_ERR("Bluetooth init failed (err %d)", err); + return 0; + } + + LOG_INF("Bluetooth initialized\n"); + + err = bt_le_scan_cb_register(&scan_callbacks); + if (err) { + LOG_ERR("Scan callback register failed (err %d)", err); + return 0; + } + err = bt_conn_cb_register(&conn_callbacks); + if (err) { + LOG_ERR("Conn callback register failed (err %d)", err); + return 0; + } + + sdc_hci_cmd_vs_central_acl_event_spacing_set_t event_spacing_params = { + .central_acl_event_spacing_us = 2000, + }; + + hci_vs_sdc_central_acl_event_spacing_set(&event_spacing_params); + + for (uint8_t i = 0; i < ARRAY_SIZE(conn_establishment_modes); i++) { + + active_conn_establishment_mode = conn_establishment_modes[i]; + + num_connections = 0; + ring_buf_reset(&connectable_peers_ring_buf); + + print_conn_establishment_mode(active_conn_establishment_mode); + LOG_INF("starting sample benchmark"); + + uptime_start_scan_ms = k_uptime_get(); + scan_start(); + + /* Wait until the connect callback is called for all connections */ + k_sem_take(&all_devices_connected_sem, K_FOREVER); + + scan_stop(); + + total_uptime_create_all_connections_ms = (k_uptime_get() - uptime_start_scan_ms); + + LOG_INF("%lld seconds to create %u connections", + (total_uptime_create_all_connections_ms / 1000), + CONFIG_BT_MAX_CONN); + + LOG_INF("Disconnecting connections..."); + bt_conn_foreach(BT_CONN_TYPE_LE, disconnect, NULL); + + /* Wait until the disconnect callback is called for all connections */ + k_sem_take(&all_devices_disconnected_sem, K_FOREVER); + LOG_INF("---------------------------------------------------------------------"); + LOG_INF("---------------------------------------------------------------------"); + } +} diff --git a/samples/bluetooth/scanning_while_connecting/sysbuild/ipc_radio/prj.conf b/samples/bluetooth/scanning_while_connecting/sysbuild/ipc_radio/prj.conf new file mode 100644 index 000000000000..e2809c1d553b --- /dev/null +++ b/samples/bluetooth/scanning_while_connecting/sysbuild/ipc_radio/prj.conf @@ -0,0 +1,25 @@ +CONFIG_IPC_SERVICE=y +CONFIG_MBOX=y + +CONFIG_HEAP_MEM_POOL_SIZE=8192 +CONFIG_MAIN_STACK_SIZE=2048 +CONFIG_SYSTEM_WORKQUEUE_STACK_SIZE=2048 + +CONFIG_ASSERT=y +CONFIG_DEBUG_INFO=y +CONFIG_EXCEPTION_STACK_TRACE=y + +CONFIG_BT=y +CONFIG_BT_HCI_RAW=y +CONFIG_BT_MAX_CONN=16 +CONFIG_BT_PERIPHERAL=n +CONFIG_BT_SCAN_AND_INITIATE_IN_PARALLEL=y +CONFIG_BT_CTLR_SDC_ALLOW_PARALLEL_SCANNING_AND_INITIATING=y +CONFIG_BT_CTLR_FAL_SIZE=255 + +CONFIG_BT_EXT_ADV=y +CONFIG_BT_CTLR_ADV_EXT=y +CONFIG_BT_CTLR_ASSERT_HANDLER=y + +CONFIG_IPC_RADIO_BT=y +CONFIG_IPC_RADIO_BT_HCI_IPC=y