diff --git a/doc/connectivity/bluetooth/api/audio/bluetooth-le-audio-arch.rst b/doc/connectivity/bluetooth/api/audio/bluetooth-le-audio-arch.rst index 7eac2171a3280..54274b2135f0b 100644 --- a/doc/connectivity/bluetooth/api/audio/bluetooth-le-audio-arch.rst +++ b/doc/connectivity/bluetooth/api/audio/bluetooth-le-audio-arch.rst @@ -726,8 +726,8 @@ Bluetooth Audio Stack. | | | | | - Shell Module | - Sample Application (in progress) | | | | | | - BSIM test | | | +-------------------------------+---------+------------------+-----------------------+--------------------------------------------------+ - | | Call Control Client | 1.0 | 3.0 | - Feature complete | - API refactor | - | | | | | - Shell Module | - Sample Application | + | | Call Control Client | 1.0 | 3.0 | - Feature complete | - API refactor (in progress) | + | | | | | - Shell Module | - Sample Application (in progress) | | | | | | - BSIM test | | +--------+-------------------------------+---------+------------------+-----------------------+--------------------------------------------------+ | MCP | Media Control Server | 1.0 | 3.0 | - Feature complete | - API refactor | diff --git a/doc/connectivity/bluetooth/shell/audio/ccp.rst b/doc/connectivity/bluetooth/shell/audio/ccp.rst index 1dc256a78befe..0fbf3ed2181df 100644 --- a/doc/connectivity/bluetooth/shell/audio/ccp.rst +++ b/doc/connectivity/bluetooth/shell/audio/ccp.rst @@ -33,3 +33,28 @@ Setup Registered GTBS bearer Registered bearer[1] uart:~$ bt connect xx:xx:xx:xx:xx:xx public + +Call Control Client +******************* +The Call Control Client is a role that typically resides on resource constrained devices such as +earbuds or headsets. + +Using the Call Control Client +============================= +The Client can control a remote CCP server device. +For example a remote device may have an incoming call that can be accepted by the Client. + +.. code-block:: console + + uart:~$ ccp_call_control_client --help + ccp_call_control_client - Bluetooth CCP Call Control Client shell commands + Subcommands: + discover : Discover GTBS and TBS on remote device + +Example Usage when connected +============================ + +.. code-block:: console + + uart:~$ ccp_call_control_client discover + Discovery completed with GTBS and 1 TBS bearers diff --git a/include/zephyr/bluetooth/audio/ccp.h b/include/zephyr/bluetooth/audio/ccp.h index 4bd8b53fd8c72..190d58313f272 100644 --- a/include/zephyr/bluetooth/audio/ccp.h +++ b/include/zephyr/bluetooth/audio/ccp.h @@ -31,8 +31,12 @@ * The profile is not limited to carrier phone calls and can be used with common applications like * Discord and Teams. */ +#include +#include #include +#include +#include #ifdef __cplusplus extern "C" { @@ -90,6 +94,82 @@ int bt_ccp_call_control_server_unregister_bearer(struct bt_ccp_call_control_serv /** @} */ /* End of group bt_ccp_call_control_server */ +/** + * @defgroup bt_ccp_call_control_client CCP Call Control Client APIs + * @ingroup bt_ccp + * @{ + */ +/** Abstract Call Control Client structure. */ +struct bt_ccp_call_control_client; + +/** Abstract Call Control Client bearer structure. */ +struct bt_ccp_call_control_client_bearer; + +/** Struct with information about bearers of a client */ +struct bt_ccp_call_control_client_bearers { +#if defined(CONFIG_BT_TBS_CLIENT_GTBS) + /** The GTBS bearer. */ + struct bt_ccp_call_control_client_bearer *gtbs_bearer; +#endif /* CONFIG_BT_TBS_CLIENT_GTBS */ + +#if defined(CONFIG_BT_TBS_CLIENT_TBS) + /** Number of TBS bearers in @p tbs_bearers */ + size_t tbs_count; + + /** Array of pointers of TBS bearers */ + struct bt_ccp_call_control_client_bearer + *tbs_bearers[CONFIG_BT_CCP_CALL_CONTROL_CLIENT_BEARER_COUNT]; +#endif /* CONFIG_BT_TBS_CLIENT_TBS */ +}; + +/** + * @brief Struct to hold the Telephone Bearer Service client callbacks + * + * These can be registered for usage with bt_tbs_client_register_cb(). + */ +struct bt_ccp_call_control_client_cb { + /** + * @brief Callback function for bt_ccp_call_control_client_discover(). + * + * This callback is called once the discovery procedure is completed. + * + * @param client Call Control Client pointer. + * @param err Error value. 0 on success, GATT error on positive + * value or errno on negative value. + * @param bearers The bearers found. + */ + void (*discover)(struct bt_ccp_call_control_client *client, int err, + struct bt_ccp_call_control_client_bearers *bearers); + + /** @internal Internally used field for list handling */ + sys_snode_t _node; +}; + +int bt_ccp_call_control_client_discover(struct bt_conn *conn, + struct bt_ccp_call_control_client **out_client); + +/** + * @brief Register callbacks for the Call Control Client + * + * @param cb The callback struct + * + * @retval 0 Succsss + * @retval -EINVAL @p cb is NULL + * @retval -EEXISTS @p cb is already registered + */ +int bt_ccp_call_control_client_register_cb(struct bt_ccp_call_control_client_cb *cb); + +/** + * @brief Unregister callbacks for the Call Control Client + * + * @param cb The callback struct + * + * @retval 0 Succsss + * @retval -EINVAL @p cb is NULL + * @retval -EALREADY @p cb is not registered + */ +int bt_ccp_call_control_client_unregister_cb(struct bt_ccp_call_control_client_cb *cb); +/** @} */ /* End of group bt_ccp_call_control_client */ #ifdef __cplusplus } #endif diff --git a/samples/bluetooth/ccp_call_control_client/CMakeLists.txt b/samples/bluetooth/ccp_call_control_client/CMakeLists.txt new file mode 100644 index 0000000000000..101c16bf0d60b --- /dev/null +++ b/samples/bluetooth/ccp_call_control_client/CMakeLists.txt @@ -0,0 +1,11 @@ +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.20.0) +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project(ccp_call_control_client) + +target_sources(app PRIVATE + src/main.c +) + +zephyr_library_include_directories(${ZEPHYR_BASE}/samples/bluetooth) diff --git a/samples/bluetooth/ccp_call_control_client/Kconfig.sysbuild b/samples/bluetooth/ccp_call_control_client/Kconfig.sysbuild new file mode 100644 index 0000000000000..f37b265ecbc27 --- /dev/null +++ b/samples/bluetooth/ccp_call_control_client/Kconfig.sysbuild @@ -0,0 +1,15 @@ +# Copyright 2023 Nordic Semiconductor ASA +# SPDX-License-Identifier: Apache-2.0 + +source "share/sysbuild/Kconfig" + +config NET_CORE_BOARD + string + default "nrf5340dk/nrf5340/cpunet" if "$(BOARD)" = "nrf5340dk" + default "nrf5340_audio_dk/nrf5340/cpunet" if "$(BOARD)" = "nrf5340_audio_dk" + default "nrf5340bsim/nrf5340/cpunet" if $(BOARD_TARGET_STRING) = "NRF5340BSIM_NRF5340_CPUAPP" + +config NET_CORE_IMAGE_HCI_IPC + bool "HCI IPC image on network core" + default y + depends on NET_CORE_BOARD != "" diff --git a/samples/bluetooth/ccp_call_control_client/README.rst b/samples/bluetooth/ccp_call_control_client/README.rst new file mode 100644 index 0000000000000..db2779297883b --- /dev/null +++ b/samples/bluetooth/ccp_call_control_client/README.rst @@ -0,0 +1,78 @@ +.. zephyr:code-sample:: bluetooth_ccp_call_control_client + :name: Call Control Profile (CCP) Call Control Server + :relevant-api: bluetooth bt_ccp bt_tbs + + CCP Call Control Server sample that registers one or more TBS bearers and advertises the + TBS UUID(s). + +Overview +******** + +Application demonstrating the CCP Call Control Client functionality. +Starts by scanning for a CCP Call Control Server to connect and set up calls. + +The profile works for both GAP Central and GAP Peripheral devices, but this sample only assumes the +GAP Central role. + +This sample can be found under :zephyr_file:`samples/bluetooth/ccp_call_control_client` +in the Zephyr tree. + +Check the :zephyr:code-sample-category:`bluetooth` samples for general information. + +Requirements +************ + +* BlueZ running on the host, or +* A board with Bluetooth Low Energy 5.2 support + +Building and Running +******************** + +When building targeting an nrf52 series board with the Zephyr Bluetooth Controller, +use ``-DOVERLAY_CONFIG=overlay-bt_ll_sw_split.conf`` to enable the required feature support. + +Building for an nrf5340dk +------------------------- + +You can build both the application core image and an appropriate controller image for the network +core with: + +.. zephyr-app-commands:: + :zephyr-app: samples/bluetooth/ccp_call_control_client/ + :board: nrf5340dk/nrf5340/cpuapp + :goals: build + :west-args: --sysbuild + +If you prefer to only build the application core image, you can do so by doing instead: + +.. zephyr-app-commands:: + :zephyr-app: samples/bluetooth/ccp_call_control_client/ + :board: nrf5340dk/nrf5340/cpuapp + :goals: build + +In that case you can pair this application core image with the +:zephyr:code-sample:`bluetooth_hci_ipc` sample +:zephyr_file:`samples/bluetooth/hci_ipc/nrf5340_cpunet_iso-bt_ll_sw_split.conf` configuration. + +Building for a simulated nrf5340bsim +------------------------------------ + +Similarly to how you would for real HW, you can do: + +.. zephyr-app-commands:: + :zephyr-app: samples/bluetooth/ccp_call_control_client/ + :board: nrf5340bsim/nrf5340/cpuapp + :goals: build + :west-args: --sysbuild + +Note this will produce a Linux executable in :file:`./build/zephyr/zephyr.exe`. +For more information, check :ref:`this board documentation `. + +Building for a simulated nrf52_bsim +----------------------------------- + +.. zephyr-app-commands:: + :zephyr-app: samples/bluetooth/ccp_call_control_client/ + :board: nrf52_bsim + :goals: build + :gen-args: -DOVERLAY_CONFIG=overlay-bt_ll_sw_split.conf diff --git a/samples/bluetooth/ccp_call_control_client/boards/nrf5340_audio_dk_nrf5340_cpuapp.conf b/samples/bluetooth/ccp_call_control_client/boards/nrf5340_audio_dk_nrf5340_cpuapp.conf new file mode 100644 index 0000000000000..09fa41842ebd9 --- /dev/null +++ b/samples/bluetooth/ccp_call_control_client/boards/nrf5340_audio_dk_nrf5340_cpuapp.conf @@ -0,0 +1,4 @@ +CONFIG_BT_BUF_EVT_RX_SIZE=255 +CONFIG_BT_BUF_ACL_RX_SIZE=255 +CONFIG_BT_BUF_ACL_TX_SIZE=251 +CONFIG_BT_BUF_CMD_TX_SIZE=255 diff --git a/samples/bluetooth/ccp_call_control_client/boards/nrf5340dk_nrf5340_cpuapp.conf b/samples/bluetooth/ccp_call_control_client/boards/nrf5340dk_nrf5340_cpuapp.conf new file mode 100644 index 0000000000000..09fa41842ebd9 --- /dev/null +++ b/samples/bluetooth/ccp_call_control_client/boards/nrf5340dk_nrf5340_cpuapp.conf @@ -0,0 +1,4 @@ +CONFIG_BT_BUF_EVT_RX_SIZE=255 +CONFIG_BT_BUF_ACL_RX_SIZE=255 +CONFIG_BT_BUF_ACL_TX_SIZE=251 +CONFIG_BT_BUF_CMD_TX_SIZE=255 diff --git a/samples/bluetooth/ccp_call_control_client/overlay-bt_ll_sw_split.conf b/samples/bluetooth/ccp_call_control_client/overlay-bt_ll_sw_split.conf new file mode 100644 index 0000000000000..dce0f2c298ec1 --- /dev/null +++ b/samples/bluetooth/ccp_call_control_client/overlay-bt_ll_sw_split.conf @@ -0,0 +1,6 @@ +# Zephyr Bluetooth Controller +CONFIG_BT_LL_SW_SPLIT=y + +# Zephyr Controller tested maximum advertising data that can be set in a single HCI command +CONFIG_BT_CTLR_ADV_DATA_LEN_MAX=191 +CONFIG_BT_CTLR_SCAN_DATA_LEN_MAX=191 diff --git a/samples/bluetooth/ccp_call_control_client/prj.conf b/samples/bluetooth/ccp_call_control_client/prj.conf new file mode 100644 index 0000000000000..85549f2c321c7 --- /dev/null +++ b/samples/bluetooth/ccp_call_control_client/prj.conf @@ -0,0 +1,22 @@ +CONFIG_BT=y +CONFIG_LOG=y +CONFIG_BT_CENTRAL=y +CONFIG_BT_GATT_CLIENT=y +CONFIG_BT_GATT_AUTO_DISCOVER_CCC=y +CONFIG_BT_AUDIO=y +CONFIG_BT_EXT_ADV=y +CONFIG_BT_DEVICE_NAME="CCP Call Control Client" + +CONFIG_BT_SMP=y +CONFIG_BT_KEYS_OVERWRITE_OLDEST=y + +# CCP support +CONFIG_BT_CCP_CALL_CONTROL_CLIENT=y +CONFIG_BT_CCP_CALL_CONTROL_CLIENT_BEARER_COUNT=2 +CONFIG_BT_TBS_CLIENT_GTBS=y +CONFIG_BT_TBS_CLIENT_TBS=y +CONFIG_BT_TBS_CLIENT_MAX_TBS_INSTANCES=1 +CONFIG_UTF8=y + +# TBS Client may require up to 12 buffers +CONFIG_BT_ATT_TX_COUNT=12 diff --git a/samples/bluetooth/ccp_call_control_client/sample.yaml b/samples/bluetooth/ccp_call_control_client/sample.yaml new file mode 100644 index 0000000000000..88fc3e30aa101 --- /dev/null +++ b/samples/bluetooth/ccp_call_control_client/sample.yaml @@ -0,0 +1,30 @@ +sample: + description: Bluetooth Low Energy Call Control Profile Server sample + name: Bluetooth Low Energy Call Control Profile Server sample +tests: + sample.bluetooth.ccp_call_control_client: + harness: bluetooth + platform_allow: + - qemu_cortex_m3 + - qemu_x86 + - nrf5340dk/nrf5340/cpuapp + - nrf5340bsim/nrf5340/cpuapp + integration_platforms: + - qemu_x86 + - nrf5340dk/nrf5340/cpuapp + tags: bluetooth + sysbuild: true + sample.bluetooth.ccp_call_control_client.bt_ll_sw_split: + harness: bluetooth + platform_allow: + - nrf52_bsim + - nrf52833dk/nrf52833 + - nrf52840dk/nrf52840 + - nrf52840dongle/nrf52840 + integration_platforms: + - nrf52_bsim + - nrf52833dk/nrf52833 + - nrf52840dk/nrf52840 + - nrf52840dongle/nrf52840 + extra_args: OVERLAY_CONFIG=overlay-bt_ll_sw_split.conf + tags: bluetooth diff --git a/samples/bluetooth/ccp_call_control_client/src/main.c b/samples/bluetooth/ccp_call_control_client/src/main.c new file mode 100644 index 0000000000000..db8341204a47f --- /dev/null +++ b/samples/bluetooth/ccp_call_control_client/src/main.c @@ -0,0 +1,335 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +LOG_MODULE_REGISTER(ccp_call_control_client, CONFIG_LOG_DEFAULT_LEVEL); + +#define SEM_TIMEOUT K_SECONDS(10) + +static struct bt_conn *peer_conn; +/* client is not static as it is used for testing purposes */ +struct bt_ccp_call_control_client *client; +static struct bt_ccp_call_control_client_bearers client_bearers; + +static K_SEM_DEFINE(sem_conn_state_change, 0, 1); +static K_SEM_DEFINE(sem_security_updated, 0, 1); +static K_SEM_DEFINE(sem_ccp_action_completed, 0, 1); + +static void connected_cb(struct bt_conn *conn, uint8_t err) +{ + char addr[BT_ADDR_LE_STR_LEN]; + + (void)bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr)); + LOG_INF("Connected: %s", addr); + + k_sem_give(&sem_conn_state_change); +} + +static void disconnected_cb(struct bt_conn *conn, uint8_t reason) +{ + char addr[BT_ADDR_LE_STR_LEN]; + + if (conn != peer_conn) { + return; + } + + (void)bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr)); + LOG_INF("Disconnected: %s (reason 0x%02x)", addr, reason); + + bt_conn_unref(peer_conn); + peer_conn = NULL; + client = NULL; + memset(&client_bearers, 0, sizeof(client_bearers)); + k_sem_give(&sem_conn_state_change); +} + +static void security_changed_cb(struct bt_conn *conn, bt_security_t level, enum bt_security_err err) +{ + if (err == 0) { + k_sem_give(&sem_security_updated); + } else { + LOG_ERR("Failed to set security level: %s(%u)", bt_security_err_to_str(err), err); + } +} + +BT_CONN_CB_DEFINE(conn_callbacks) = { + .connected = connected_cb, + .disconnected = disconnected_cb, + .security_changed = security_changed_cb, +}; + +static bool check_gtbs_support(struct bt_data *data, void *user_data) +{ + struct net_buf_simple svc_data; + bool *connect = user_data; + const struct bt_uuid *uuid; + uint16_t uuid_val; + + if (data->type != BT_DATA_SVC_DATA16) { + return true; /* Continue parsing to next AD data type */ + } + + if (data->data_len < sizeof(uuid_val)) { + LOG_WRN("AD invalid size %u", data->data_len); + return true; /* Continue parsing to next AD data type */ + } + + net_buf_simple_init_with_data(&svc_data, (void *)data->data, data->data_len); + + /* Pull the 16-bit service data and compare to what we are searching for */ + uuid_val = net_buf_simple_pull_le16(&svc_data); + uuid = BT_UUID_DECLARE_16(sys_le16_to_cpu(uuid_val)); + if (bt_uuid_cmp(uuid, BT_UUID_GTBS) != 0) { + /* We are looking for the GTBS service data */ + return true; /* Continue parsing to next AD data type */ + } + + *connect = true; + + return false; /* Stop parsing */ +} + +static void scan_recv_cb(const struct bt_le_scan_recv_info *info, struct net_buf_simple *ad) +{ + char addr_str[BT_ADDR_LE_STR_LEN]; + bool connect = false; + + if (peer_conn != NULL) { + /* Already connected */ + return; + } + + /* CCP mandates that connectbale extended advertising is used by the peripherals so we + * ignore any scan report this is not that. + * We also ignore reports with poor RSSI + */ + if (info->adv_type != BT_GAP_ADV_TYPE_EXT_ADV || + (info->adv_props & BT_GAP_ADV_PROP_EXT_ADV) == 0 || + (info->adv_props & BT_GAP_ADV_PROP_CONNECTABLE) == 0 || info->rssi < -70) { + return; + } + + (void)bt_addr_le_to_str(info->addr, addr_str, sizeof(addr_str)); + LOG_INF("Connectable device found: %s (RSSI %d)", addr_str, info->rssi); + + /* Iterate on the advertising data to see if claims GTBS support */ + bt_data_parse(ad, check_gtbs_support, &connect); + + if (connect) { + int err; + + err = bt_le_scan_stop(); + if (err != 0) { + LOG_ERR("Scanning failed to stop (err %d)", err); + return; + } + + LOG_INF("Connecting to found CCP server"); + err = bt_conn_le_create(info->addr, BT_CONN_LE_CREATE_CONN, + BT_LE_CONN_PARAM_DEFAULT, &peer_conn); + if (err != 0) { + LOG_ERR("Conn create failed: %d", err); + } + } +} + +static int scan_and_connect(void) +{ + int err; + + err = bt_le_scan_start(BT_LE_SCAN_PASSIVE, NULL); + if (err != 0) { + LOG_ERR("Scanning failed to start (err %d)", err); + return err; + } + + LOG_INF("Scanning successfully started"); + + err = k_sem_take(&sem_conn_state_change, K_FOREVER); + if (err != 0) { + LOG_ERR("failed to take sem_connected (err %d)", err); + return err; + } + + err = bt_conn_set_security(peer_conn, BT_SECURITY_L2); + if (err != 0) { + LOG_ERR("failed to set security (err %d)", err); + return err; + } + + err = k_sem_take(&sem_security_updated, SEM_TIMEOUT); + if (err != 0) { + LOG_ERR("failed to take sem_security_updated (err %d)", err); + return err; + } + + LOG_INF("Security successfully updated"); + + return 0; +} + +static void ccp_call_control_client_discover_cb(struct bt_ccp_call_control_client *client, int err, + struct bt_ccp_call_control_client_bearers *bearers) +{ + if (err != 0) { + LOG_ERR("Discovery failed: %d", err); + return; + } + + LOG_INF("Discovery completed with %s%u TBS bearers", + bearers->gtbs_bearer != NULL ? "GTBS and " : "", bearers->tbs_count); + + memcpy(&client_bearers, bearers, sizeof(client_bearers)); + + k_sem_give(&sem_ccp_action_completed); +} + +static int reset_ccp_call_control_client(void) +{ + int err; + + LOG_INF("Resetting"); + + if (peer_conn != NULL) { + err = bt_conn_disconnect(peer_conn, BT_HCI_ERR_REMOTE_USER_TERM_CONN); + if (err != 0) { + return err; + } + + err = k_sem_take(&sem_conn_state_change, K_FOREVER); + if (err != 0) { + LOG_ERR("Failed to take sem_conn_state_change: %d", err); + return err; + } + } + + /* If scanning is already stopped it will still return `0` */ + err = bt_le_scan_stop(); + if (err != 0) { + LOG_ERR("Scanning failed to stop (err %d)", err); + return err; + } + + k_sem_reset(&sem_conn_state_change); + + return 0; +} + +static int discover_services(void) +{ + int err; + + LOG_INF("Discovering GTBS and TBS"); + + err = bt_ccp_call_control_client_discover(peer_conn, &client); + if (err != 0) { + LOG_ERR("Failed to discover: %d", err); + return err; + } + + err = k_sem_take(&sem_ccp_action_completed, SEM_TIMEOUT); + if (err != 0) { + LOG_ERR("Failed to take sem_ccp_action_completed: %d", err); + return err; + } + + return 0; +} + +static int init_ccp_call_control_client(void) +{ + static struct bt_le_scan_cb scan_cbs = { + .recv = scan_recv_cb, + }; + static struct bt_ccp_call_control_client_cb ccp_call_control_client_cbs = { + .discover = ccp_call_control_client_discover_cb, + }; + int err; + + err = bt_enable(NULL); + if (err != 0) { + LOG_ERR("Bluetooth enable failed (err %d)", err); + + return err; + } + + LOG_DBG("Bluetooth initialized"); + err = bt_le_scan_cb_register(&scan_cbs); + if (err != 0) { + LOG_ERR("Bluetooth enable failed (err %d)", err); + + return err; + } + + err = bt_ccp_call_control_client_register_cb(&ccp_call_control_client_cbs); + if (err != 0) { + LOG_ERR("Bluetooth enable failed (err %d)", err); + + return err; + } + + return 0; +} + +int main(void) +{ + int err; + + err = init_ccp_call_control_client(); + if (err != 0) { + return 0; + } + + LOG_INF("CCP Call Control Client initialized"); + + while (true) { + err = reset_ccp_call_control_client(); + if (err != 0) { + break; + } + + /* Start scanning for CCP servers and connect to the first we find */ + err = scan_and_connect(); + if (err != 0) { + continue; + } + + /* Discover TBS and GTBS on the remove server */ + err = discover_services(); + if (err != 0) { + continue; + } + + /* Reset if disconnected */ + err = k_sem_take(&sem_conn_state_change, K_FOREVER); + if (err != 0) { + LOG_ERR("Failed to take sem_conn_state_change: err %d", err); + break; + } + } + + return 0; +} diff --git a/samples/bluetooth/ccp_call_control_client/sysbuild.cmake b/samples/bluetooth/ccp_call_control_client/sysbuild.cmake new file mode 100644 index 0000000000000..2523aac8ea76f --- /dev/null +++ b/samples/bluetooth/ccp_call_control_client/sysbuild.cmake @@ -0,0 +1,24 @@ +# Copyright (c) 2023 Nordic Semiconductor ASA +# SPDX-License-Identifier: Apache-2.0 + +if(SB_CONFIG_NET_CORE_IMAGE_HCI_IPC) + # For builds in the nrf5340, we build the netcore image with the controller + + set(NET_APP hci_ipc) + set(NET_APP_SRC_DIR ${ZEPHYR_BASE}/samples/bluetooth/${NET_APP}) + + ExternalZephyrProject_Add( + APPLICATION ${NET_APP} + SOURCE_DIR ${NET_APP_SRC_DIR} + BOARD ${SB_CONFIG_NET_CORE_BOARD} + ) + + set(${NET_APP}_CONF_FILE + ${NET_APP_SRC_DIR}/nrf5340_cpunet_iso-bt_ll_sw_split.conf + CACHE INTERNAL "" + ) + + native_simulator_set_child_images(${DEFAULT_IMAGE} ${NET_APP}) +endif() + +native_simulator_set_final_executable(${DEFAULT_IMAGE}) diff --git a/subsys/bluetooth/audio/CMakeLists.txt b/subsys/bluetooth/audio/CMakeLists.txt index 86f507746be2d..a7537140a64e5 100644 --- a/subsys/bluetooth/audio/CMakeLists.txt +++ b/subsys/bluetooth/audio/CMakeLists.txt @@ -58,6 +58,7 @@ zephyr_library_sources_ifdef(CONFIG_BT_BAP_BROADCAST_SOURCE bap_broadcast_source zephyr_library_sources_ifdef(CONFIG_BT_BAP_BROADCAST_SINK bap_broadcast_sink.c) zephyr_library_sources_ifdef(CONFIG_BT_BAP_SCAN_DELEGATOR bap_scan_delegator.c) zephyr_library_sources_ifdef(CONFIG_BT_BAP_BROADCAST_ASSISTANT bap_broadcast_assistant.c) +zephyr_library_sources_ifdef(CONFIG_BT_CCP_CALL_CONTROL_CLIENT ccp_call_control_client.c) zephyr_library_sources_ifdef(CONFIG_BT_CCP_CALL_CONTROL_SERVER ccp_call_control_server.c) zephyr_library_sources_ifdef(CONFIG_BT_HAS has.c) zephyr_library_sources_ifdef(CONFIG_BT_HAS_CLIENT has_client.c) diff --git a/subsys/bluetooth/audio/Kconfig.ccp b/subsys/bluetooth/audio/Kconfig.ccp index f43557e37a869..c397b831cfc01 100644 --- a/subsys/bluetooth/audio/Kconfig.ccp +++ b/subsys/bluetooth/audio/Kconfig.ccp @@ -7,6 +7,31 @@ if BT_AUDIO +config BT_CCP_CALL_CONTROL_CLIENT + bool "Call Control Profile Client Support" + depends on BT_EXT_ADV + depends on BT_TBS_CLIENT + depends on BT_BONDABLE + help + This option enables support for the Call Control Profile Client which uses the Telephone + Bearer Service (TBS) client to control calls on a remote device. + +if BT_CCP_CALL_CONTROL_CLIENT + +config BT_CCP_CALL_CONTROL_CLIENT_BEARER_COUNT + int "Telephone bearer count" + default 1 + range 1 $(UINT8_MAX) if BT_TBS_CLIENT_TBS + range 1 1 + help + The number of supported telephone bearers on the CCP Call Control Client + +module = BT_CCP_CALL_CONTROL_CLIENT +module-str = "Call Control Profile Client" +source "subsys/logging/Kconfig.template.log_config" + +endif # BT_CCP_CALL_CONTROL_CLIENT + config BT_CCP_CALL_CONTROL_SERVER bool "Call Control Profile Call Control Server Support" depends on BT_EXT_ADV diff --git a/subsys/bluetooth/audio/ccp_call_control_client.c b/subsys/bluetooth/audio/ccp_call_control_client.c new file mode 100644 index 0000000000000..641f887c3f483 --- /dev/null +++ b/subsys/bluetooth/audio/ccp_call_control_client.c @@ -0,0 +1,234 @@ +/* Bluetooth CCP - Call Control Profile Call Control Server + * + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +LOG_MODULE_REGISTER(bt_ccp_call_control_client, CONFIG_BT_CCP_CALL_CONTROL_CLIENT_LOG_LEVEL); + +static sys_slist_t ccp_call_control_client_cbs = + SYS_SLIST_STATIC_INIT(&ccp_call_control_client_cbs); + +static struct bt_tbs_client_cb tbs_client_cbs; + +/* A service instance can either be a GTBS or a TBS insttance */ +struct bt_ccp_call_control_client_bearer { + uint8_t tbs_index; + bool discovered; +}; + +enum ccp_call_control_client_flag { + CCP_CALL_CONTROL_CLIENT_FLAG_BUSY, + + CCP_CALL_CONTROL_CLIENT_FLAG_NUM_FLAGS, /* keep as last */ +}; + +struct bt_ccp_call_control_client { + struct bt_ccp_call_control_client_bearer + bearers[CONFIG_BT_CCP_CALL_CONTROL_CLIENT_BEARER_COUNT]; + struct bt_conn *conn; + + ATOMIC_DEFINE(flags, CCP_CALL_CONTROL_CLIENT_FLAG_NUM_FLAGS); +}; + +static struct bt_ccp_call_control_client clients[CONFIG_BT_MAX_CONN]; + +static struct bt_ccp_call_control_client *get_client_by_conn(const struct bt_conn *conn) +{ + return &clients[bt_conn_index(conn)]; +} + +static void connected_cb(struct bt_conn *conn, uint8_t err) +{ + static bool cbs_registered; + + /* We register the callbacks in the connected callback. That way we ensure that they are + * registered before any procedures are completed or we receive any notifications, while + * registering them as late as possible + */ + if (err == BT_HCI_ERR_SUCCESS && !cbs_registered) { + int cb_err; + + cb_err = bt_tbs_client_register_cb(&tbs_client_cbs); + __ASSERT(cb_err == 0, "Failed to register TBS callbacks: %d", cb_err); + + cbs_registered = true; + } +} + +static void disconnected_cb(struct bt_conn *conn, uint8_t reason) +{ + struct bt_ccp_call_control_client *client = get_client_by_conn(conn); + + /* client->conn may be NULL */ + if (client->conn == conn) { + bt_conn_unref(client->conn); + client->conn = NULL; + } +} + +BT_CONN_CB_DEFINE(conn_callbacks) = { + .connected = connected_cb, + .disconnected = disconnected_cb, +}; + +static void populate_bearers(struct bt_ccp_call_control_client *client, + struct bt_ccp_call_control_client_bearers *bearers) +{ + size_t i = 0; + +#if defined(CONFIG_BT_TBS_CLIENT_GTBS) + if (client->bearers[i].discovered) { + bearers->gtbs_bearer = &client->bearers[i++]; + } +#endif /* CONFIG_BT_TBS_CLIENT_GTBS */ + +#if defined(CONFIG_BT_TBS_CLIENT_TBS) + for (; i < ARRAY_SIZE(client->bearers); i++) { + if (!client->bearers[i].discovered) { + break; + } + + bearers->tbs_bearers[bearers->tbs_count++] = &client->bearers[i]; + } +#endif /* CONFIG_BT_TBS_CLIENT_TBS */ +} + +static void tbs_client_discover_cb(struct bt_conn *conn, int err, uint8_t tbs_count, + bool gtbs_found) +{ + struct bt_ccp_call_control_client *client = get_client_by_conn(conn); + struct bt_ccp_call_control_client_bearers bearers = {0}; + struct bt_ccp_call_control_client_cb *listener, *next; + + LOG_DBG("conn %p err %d tbs_count %u gtbs_found %d", (void *)conn, err, tbs_count, + gtbs_found); + + memset(client->bearers, 0, sizeof((client->bearers))); + + if (IS_ENABLED(CONFIG_BT_TBS_CLIENT_GTBS) && gtbs_found) { + client->bearers[0].discovered = true; + client->bearers[0].tbs_index = BT_TBS_GTBS_INDEX; + } + + if (IS_ENABLED(CONFIG_BT_TBS_CLIENT_TBS)) { + for (uint8_t i = 0U; i < tbs_count; i++) { + const uint8_t idx = i + (gtbs_found ? 1 : 0); + + if (idx >= ARRAY_SIZE(client->bearers)) { + LOG_WRN("Discoverd more TBS instances (%u) than the CCP Call " + "Control Client supports %zu", + tbs_count, ARRAY_SIZE(client->bearers)); + break; + } + + client->bearers[idx].discovered = true; + client->bearers[idx].tbs_index = i; + } + } + + populate_bearers(client, &bearers); + + atomic_clear_bit(client->flags, CCP_CALL_CONTROL_CLIENT_FLAG_BUSY); + + SYS_SLIST_FOR_EACH_CONTAINER_SAFE(&ccp_call_control_client_cbs, listener, next, _node) { + if (listener->discover != NULL) { + listener->discover(client, err, &bearers); + } + } +} + +int bt_ccp_call_control_client_discover(struct bt_conn *conn, + struct bt_ccp_call_control_client **out_client) +{ + struct bt_ccp_call_control_client *client; + int err; + + CHECKIF(conn == NULL) { + LOG_DBG("conn is NULL"); + + return -EINVAL; + } + + CHECKIF(out_client == NULL) { + LOG_DBG("client is NULL"); + + return -EINVAL; + } + + client = get_client_by_conn(conn); + if (atomic_test_and_set_bit(client->flags, CCP_CALL_CONTROL_CLIENT_FLAG_BUSY)) { + return -EBUSY; + } + + tbs_client_cbs.discover = tbs_client_discover_cb; + + err = bt_tbs_client_discover(conn); + if (err != 0) { + LOG_DBG("Failed to discover TBS for %p: %d", (void *)conn, err); + + atomic_clear_bit(client->flags, CCP_CALL_CONTROL_CLIENT_FLAG_BUSY); + + /* Return known errors */ + if (err == -EBUSY) { + return err; + } + + return -ENOEXEC; + } + + client->conn = bt_conn_ref(conn); + *out_client = client; + + return 0; +} + +int bt_ccp_call_control_client_register_cb(struct bt_ccp_call_control_client_cb *cb) +{ + CHECKIF(cb == NULL) { + LOG_DBG("cb is NULL"); + + return -EINVAL; + } + + if (sys_slist_find(&ccp_call_control_client_cbs, &cb->_node, NULL)) { + return -EEXIST; + } + + sys_slist_append(&ccp_call_control_client_cbs, &cb->_node); + + return 0; +} + +int bt_ccp_call_control_client_unregister_cb(struct bt_ccp_call_control_client_cb *cb) +{ + CHECKIF(cb == NULL) { + LOG_DBG("cb is NULL"); + return -EINVAL; + } + + if (!sys_slist_find_and_remove(&ccp_call_control_client_cbs, &cb->_node)) { + return -EALREADY; + } + + return 0; +} diff --git a/subsys/bluetooth/audio/shell/CMakeLists.txt b/subsys/bluetooth/audio/shell/CMakeLists.txt index 5fe6a71b61509..bb71e1e65c0ae 100644 --- a/subsys/bluetooth/audio/shell/CMakeLists.txt +++ b/subsys/bluetooth/audio/shell/CMakeLists.txt @@ -7,6 +7,10 @@ zephyr_library_sources_ifdef( CONFIG_BT_CCP_CALL_CONTROL_SERVER ccp_call_control_server.c ) +zephyr_library_sources_ifdef( + CONFIG_BT_CCP_CALL_CONTROL_CLIENT + ccp_call_control_client.c + ) zephyr_library_sources_ifdef( CONFIG_BT_VCP_VOL_REND vcp_vol_rend.c diff --git a/subsys/bluetooth/audio/shell/ccp_call_control_client.c b/subsys/bluetooth/audio/shell/ccp_call_control_client.c new file mode 100644 index 0000000000000..bcfa418b890e1 --- /dev/null +++ b/subsys/bluetooth/audio/shell/ccp_call_control_client.c @@ -0,0 +1,100 @@ +/** @file + * @brief Bluetooth Call Control Profile Call Control Client shell + */ + +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "common/bt_shell_private.h" +#include "host/shell/bt.h" + +static struct bt_ccp_call_control_client *clients[CONFIG_BT_MAX_CONN]; + +static void ccp_call_control_client_discover_cb(struct bt_ccp_call_control_client *client, int err, + struct bt_ccp_call_control_client_bearers *bearers) +{ + struct bt_ccp_call_control_client_bearer *gtbs_bearer = NULL; + uint8_t tbs_count = 0U; + + if (err != 0) { + bt_shell_error("Failed to discover TBS: %d", err); + return; + } +#if defined(CONFIG_BT_TBS_CLIENT_GTBS) + gtbs_bearer = bearers->gtbs_bearer; +#endif /* CONFIG_BT_TBS_CLIENT_GTBS */ +#if defined(CONFIG_BT_TBS_CLIENT_TBS) + tbs_count = bearers->tbs_count; +#endif /* CONFIG_BT_TBS_CLIENT_TBS */ + + bt_shell_info("Discovery completed with %s%u TBS bearers", + gtbs_bearer != NULL ? "GTBS and " : "", tbs_count); +} + +static int cmd_ccp_call_control_client_discover(const struct shell *sh, size_t argc, char *argv[]) +{ + static bool cb_registered; + + int err; + + if (default_conn == NULL) { + shell_error(sh, "Not connected"); + return -ENOEXEC; + } + + if (!cb_registered) { + static struct bt_ccp_call_control_client_cb ccp_call_control_client_cbs = { + .discover = ccp_call_control_client_discover_cb, + }; + + err = bt_ccp_call_control_client_register_cb(&ccp_call_control_client_cbs); + if (err != 0) { + shell_error(sh, "Failed to register CCP Call Control Client cbs (err %d)", + err); + return -ENOEXEC; + } + } + + err = bt_ccp_call_control_client_discover(default_conn, + &clients[bt_conn_index(default_conn)]); + if (err != 0) { + shell_error(sh, "Failed to discover GTBS: %d", err); + + return -ENOEXEC; + } + + return 0; +} + +static int cmd_ccp_call_control_client(const struct shell *sh, size_t argc, char **argv) +{ + if (argc > 1) { + shell_error(sh, "%s unknown parameter: %s", argv[0], argv[1]); + } else { + shell_error(sh, "%s Missing subcommand", argv[0]); + } + + return -ENOEXEC; +} + +SHELL_STATIC_SUBCMD_SET_CREATE(ccp_call_control_client_cmds, + SHELL_CMD_ARG(discover, NULL, + "Discover GTBS and TBS on remote device", + cmd_ccp_call_control_client_discover, 1, 0), + SHELL_SUBCMD_SET_END); + +SHELL_CMD_ARG_REGISTER(ccp_call_control_client, &ccp_call_control_client_cmds, + "Bluetooth CCP Call Control Client shell commands", + cmd_ccp_call_control_client, 1, 1); diff --git a/subsys/bluetooth/audio/tbs_client.c b/subsys/bluetooth/audio/tbs_client.c index db8e081f83ec3..f6e633d38e705 100644 --- a/subsys/bluetooth/audio/tbs_client.c +++ b/subsys/bluetooth/audio/tbs_client.c @@ -19,7 +19,6 @@ #include #include #include -#include #include #include #include diff --git a/tests/bluetooth/audio/ccp_call_control_client/CMakeLists.txt b/tests/bluetooth/audio/ccp_call_control_client/CMakeLists.txt new file mode 100644 index 0000000000000..05e846d7878e2 --- /dev/null +++ b/tests/bluetooth/audio/ccp_call_control_client/CMakeLists.txt @@ -0,0 +1,17 @@ +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.20.0) + +project(bluetooth_ccp) +find_package(Zephyr COMPONENTS unittest HINTS $ENV{ZEPHYR_BASE}) + +add_subdirectory(${ZEPHYR_BASE}/tests/bluetooth/audio/ccp_call_control_client/uut uut) + +target_link_libraries(testbinary PRIVATE uut) + +target_include_directories(testbinary PRIVATE include) + +target_sources(testbinary + PRIVATE + src/main.c +) diff --git a/tests/bluetooth/audio/ccp_call_control_client/include/ccp_call_control_client.h b/tests/bluetooth/audio/ccp_call_control_client/include/ccp_call_control_client.h new file mode 100644 index 0000000000000..998c9f99852bb --- /dev/null +++ b/tests/bluetooth/audio/ccp_call_control_client/include/ccp_call_control_client.h @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef MOCKS_CCP_CALL_CONTROL_CLIENT_H_ +#define MOCKS_CCP_CALL_CONTROL_CLIENT_H_ + +#include + +#include +#include +#include + +extern struct bt_ccp_call_control_client_cb mock_ccp_call_control_client_cb; + +void mock_ccp_call_control_client_init(void); +void mock_ccp_call_control_client_cleanup(void); + +DECLARE_FAKE_VOID_FUNC(mock_ccp_call_control_client_discover_cb, + struct bt_ccp_call_control_client *, int, + struct bt_ccp_call_control_client_bearers *); + +#endif /* MOCKS_CCP_CALL_CONTROL_CLIENT_H_ */ diff --git a/tests/bluetooth/audio/ccp_call_control_client/prj.conf b/tests/bluetooth/audio/ccp_call_control_client/prj.conf new file mode 100644 index 0000000000000..5f0f8a3f2a9f7 --- /dev/null +++ b/tests/bluetooth/audio/ccp_call_control_client/prj.conf @@ -0,0 +1,21 @@ +CONFIG_ZTEST=y + +CONFIG_BT=y +CONFIG_BT_EXT_ADV=y +CONFIG_BT_SMP=y +CONFIG_BT_PERIPHERAL=y +CONFIG_BT_GATT_CLIENT=y +CONFIG_BT_GATT_AUTO_DISCOVER_CCC=y +CONFIG_BT_AUDIO=y +CONFIG_BT_CCP_CALL_CONTROL_CLIENT=y +CONFIG_BT_CCP_CALL_CONTROL_CLIENT_BEARER_COUNT=2 +CONFIG_BT_TBS_CLIENT_TBS=y +CONFIG_BT_TBS_CLIENT_GTBS=y +CONFIG_UTF8=y + +CONFIG_ASSERT=y +CONFIG_ASSERT_LEVEL=2 +CONFIG_ASSERT_VERBOSE=y + +CONFIG_LOG=y +CONFIG_BT_CCP_CALL_CONTROL_CLIENT_LOG_LEVEL_DBG=y diff --git a/tests/bluetooth/audio/ccp_call_control_client/src/main.c b/tests/bluetooth/audio/ccp_call_control_client/src/main.c new file mode 100644 index 0000000000000..34b569d935934 --- /dev/null +++ b/tests/bluetooth/audio/ccp_call_control_client/src/main.c @@ -0,0 +1,212 @@ +/* main.c - Application main entry point */ + +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "conn.h" +#include "ccp_call_control_client.h" +#include "expects_util.h" + +DEFINE_FFF_GLOBALS; + +struct ccp_call_control_client_test_suite_fixture { + /** Need 1 additional bearer than the max to trigger some corner cases */ + struct bt_ccp_call_control_client_bearer + *bearers[CONFIG_BT_CCP_CALL_CONTROL_CLIENT_BEARER_COUNT + 1]; + struct bt_ccp_call_control_client *client; + struct bt_conn conn; +}; + +static void mock_init_rule_before(const struct ztest_unit_test *test, void *fixture) +{ + mock_ccp_call_control_client_init(); +} + +static void mock_destroy_rule_after(const struct ztest_unit_test *test, void *fixture) +{ + mock_ccp_call_control_client_cleanup(); +} + +ZTEST_RULE(mock_rule, mock_init_rule_before, mock_destroy_rule_after); + +static void test_conn_init(struct bt_conn *conn) +{ + conn->index = 0; + conn->info.type = BT_CONN_TYPE_LE; + conn->info.role = BT_CONN_ROLE_CENTRAL; + conn->info.state = BT_CONN_STATE_CONNECTED; + conn->info.security.level = BT_SECURITY_L2; + conn->info.security.enc_key_size = BT_ENC_KEY_SIZE_MAX; + conn->info.security.flags = BT_SECURITY_FLAG_OOB | BT_SECURITY_FLAG_SC; + + mock_bt_conn_connected(conn, BT_HCI_ERR_SUCCESS); +} + +static void *ccp_call_control_client_test_suite_setup(void) +{ + struct ccp_call_control_client_test_suite_fixture *fixture; + + fixture = malloc(sizeof(*fixture)); + zassert_not_null(fixture); + + return fixture; +} + +static void ccp_call_control_client_test_suite_before(void *f) +{ + struct ccp_call_control_client_test_suite_fixture *fixture = f; + + memset(fixture, 0, sizeof(*fixture)); + test_conn_init(&fixture->conn); +} + +static void ccp_call_control_client_test_suite_after(void *f) +{ + struct ccp_call_control_client_test_suite_fixture *fixture = f; + + (void)bt_ccp_call_control_client_unregister_cb(&mock_ccp_call_control_client_cb); + mock_bt_conn_disconnected(&fixture->conn, BT_HCI_ERR_REMOTE_USER_TERM_CONN); +} + +static void ccp_call_control_client_test_suite_teardown(void *f) +{ + free(f); +} + +ZTEST_SUITE(ccp_call_control_client_test_suite, NULL, ccp_call_control_client_test_suite_setup, + ccp_call_control_client_test_suite_before, ccp_call_control_client_test_suite_after, + ccp_call_control_client_test_suite_teardown); + +static ZTEST_F(ccp_call_control_client_test_suite, test_ccp_call_control_client_register_cb) +{ + int err; + + err = bt_ccp_call_control_client_register_cb(&mock_ccp_call_control_client_cb); + zassert_equal(0, err, "Unexpected return value %d", err); +} + +static ZTEST_F(ccp_call_control_client_test_suite, + test_ccp_call_control_client_register_cb_inval_param_null) +{ + int err; + + err = bt_ccp_call_control_client_register_cb(NULL); + zassert_equal(-EINVAL, err, "Unexpected return value %d", err); +} + +static ZTEST_F(ccp_call_control_client_test_suite, + test_ccp_call_control_client_register_cb_inval_double_register) +{ + int err; + + err = bt_ccp_call_control_client_register_cb(&mock_ccp_call_control_client_cb); + zassert_equal(0, err, "Unexpected return value %d", err); + + err = bt_ccp_call_control_client_register_cb(&mock_ccp_call_control_client_cb); + zassert_equal(-EEXIST, err, "Unexpected return value %d", err); +} + +static ZTEST_F(ccp_call_control_client_test_suite, test_ccp_call_control_client_unregister_cb) +{ + int err; + + err = bt_ccp_call_control_client_register_cb(&mock_ccp_call_control_client_cb); + zassert_equal(0, err, "Unexpected return value %d", err); + + err = bt_ccp_call_control_client_unregister_cb(&mock_ccp_call_control_client_cb); + zassert_equal(0, err, "Unexpected return value %d", err); +} + +static ZTEST_F(ccp_call_control_client_test_suite, + test_ccp_call_control_client_unregister_cb_inval_param_null) +{ + int err; + + err = bt_ccp_call_control_client_unregister_cb(NULL); + zassert_equal(-EINVAL, err, "Unexpected return value %d", err); +} + +static ZTEST_F(ccp_call_control_client_test_suite, + test_ccp_call_control_client_unregister_cb_inval_double_unregister) +{ + int err; + + err = bt_ccp_call_control_client_register_cb(&mock_ccp_call_control_client_cb); + zassert_equal(0, err, "Unexpected return value %d", err); + + err = bt_ccp_call_control_client_unregister_cb(&mock_ccp_call_control_client_cb); + zassert_equal(0, err, "Unexpected return value %d", err); + + err = bt_ccp_call_control_client_unregister_cb(&mock_ccp_call_control_client_cb); + zassert_equal(-EALREADY, err, "Unexpected return value %d", err); +} + +static ZTEST_F(ccp_call_control_client_test_suite, test_ccp_call_control_client_discover) +{ + int err; + + err = bt_ccp_call_control_client_register_cb(&mock_ccp_call_control_client_cb); + zassert_equal(0, err, "Unexpected return value %d", err); + + err = bt_ccp_call_control_client_discover(&fixture->conn, &fixture->client); + zassert_equal(0, err, "Unexpected return value %d", err); + + /* Validate that we got the callback with valid values */ + zexpect_call_count("bt_ccp_call_control_client_cb.discover", 1, + mock_ccp_call_control_client_discover_cb_fake.call_count); + zassert_not_null(mock_ccp_call_control_client_discover_cb_fake.arg0_history[0]); + zassert_equal(0, mock_ccp_call_control_client_discover_cb_fake.arg1_history[0]); + zassert_not_null(mock_ccp_call_control_client_discover_cb_fake.arg2_history[0]); + +#if defined(CONFIG_BT_TBS_CLIENT_GTBS) + zassert_not_null( + mock_ccp_call_control_client_discover_cb_fake.arg2_history[0]->gtbs_bearer); +#endif /* CONFIG_BT_TBS_CLIENT_GTBS */ + +#if defined(CONFIG_BT_TBS_CLIENT_TBS) + zassert_equal(CONFIG_BT_TBS_CLIENT_MAX_TBS_INSTANCES, + mock_ccp_call_control_client_discover_cb_fake.arg2_history[0]->tbs_count); + zassert_not_null( + mock_ccp_call_control_client_discover_cb_fake.arg2_history[0]->tbs_bearers); +#endif /* CONFIG_BT_TBS_CLIENT_TBS */ +} + +static ZTEST_F(ccp_call_control_client_test_suite, + test_ccp_call_control_client_discover_inval_param_null_conn) +{ + int err; + + err = bt_ccp_call_control_client_register_cb(&mock_ccp_call_control_client_cb); + zassert_equal(0, err, "Unexpected return value %d", err); + + err = bt_ccp_call_control_client_discover(NULL, &fixture->client); + zassert_equal(-EINVAL, err, "Unexpected return value %d", err); +} + +static ZTEST_F(ccp_call_control_client_test_suite, + test_ccp_call_control_client_discover_inval_param_null_client) +{ + int err; + + err = bt_ccp_call_control_client_register_cb(&mock_ccp_call_control_client_cb); + zassert_equal(0, err, "Unexpected return value %d", err); + + err = bt_ccp_call_control_client_discover(&fixture->conn, NULL); + zassert_equal(-EINVAL, err, "Unexpected return value %d", err); +} diff --git a/tests/bluetooth/audio/ccp_call_control_client/testcase.yaml b/tests/bluetooth/audio/ccp_call_control_client/testcase.yaml new file mode 100644 index 0000000000000..007a4f45ef577 --- /dev/null +++ b/tests/bluetooth/audio/ccp_call_control_client/testcase.yaml @@ -0,0 +1,7 @@ +common: + tags: + - bluetooth + - bluetooth_audio +tests: + bluetooth.audio.ccp_call_control_client.test: + type: unit diff --git a/tests/bluetooth/audio/ccp_call_control_client/uut/CMakeLists.txt b/tests/bluetooth/audio/ccp_call_control_client/uut/CMakeLists.txt new file mode 100644 index 0000000000000..1347cc8e0f195 --- /dev/null +++ b/tests/bluetooth/audio/ccp_call_control_client/uut/CMakeLists.txt @@ -0,0 +1,22 @@ +# +# Copyright (c) 2024 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: Apache-2.0 +# +# CMakeLists.txt file for creating of uut library. +# + +add_library(uut STATIC + ${ZEPHYR_BASE}/lib/net_buf/buf_simple.c + ${ZEPHYR_BASE}/subsys/bluetooth/audio/ccp_call_control_client.c + ${ZEPHYR_BASE}/subsys/logging/log_minimal.c + ccp_call_control_client.c + tbs_client.c +) + +add_subdirectory(${ZEPHYR_BASE}/tests/bluetooth/audio/mocks mocks) + +target_link_libraries(uut PUBLIC test_interface mocks) +target_include_directories(uut PRIVATE ${ZEPHYR_BASE}/tests/bluetooth/audio/ccp_call_control_client/include) + +target_compile_options(uut PRIVATE -std=c11 -include ztest.h) diff --git a/tests/bluetooth/audio/ccp_call_control_client/uut/ccp_call_control_client.c b/tests/bluetooth/audio/ccp_call_control_client/uut/ccp_call_control_client.c new file mode 100644 index 0000000000000..5b4e236321722 --- /dev/null +++ b/tests/bluetooth/audio/ccp_call_control_client/uut/ccp_call_control_client.c @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include + +#include +#include +#include + +#include "ccp_call_control_client.h" + +/* List of fakes used by this unit tester */ +#define FFF_FAKES_LIST(FAKE) FAKE(mock_ccp_call_control_client_discover_cb) + +DEFINE_FAKE_VOID_FUNC(mock_ccp_call_control_client_discover_cb, struct bt_ccp_call_control_client *, + int, struct bt_ccp_call_control_client_bearers *); + +struct bt_ccp_call_control_client_cb mock_ccp_call_control_client_cb = { + .discover = mock_ccp_call_control_client_discover_cb, +}; + +void mock_ccp_call_control_client_init(void) +{ + FFF_FAKES_LIST(RESET_FAKE); +} + +void mock_ccp_call_control_client_cleanup(void) +{ +} diff --git a/tests/bluetooth/audio/ccp_call_control_client/uut/tbs_client.c b/tests/bluetooth/audio/ccp_call_control_client/uut/tbs_client.c new file mode 100644 index 0000000000000..87564da458758 --- /dev/null +++ b/tests/bluetooth/audio/ccp_call_control_client/uut/tbs_client.c @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include +#include + +#include +#include +#include + +static struct bt_tbs_client_cb *tbs_cbs; + +int bt_tbs_client_register_cb(struct bt_tbs_client_cb *cbs) +{ + tbs_cbs = cbs; + + return 0; +} + +int bt_tbs_client_discover(struct bt_conn *conn) +{ + if (tbs_cbs != NULL && tbs_cbs->discover != NULL) { + uint8_t tbs_cnt = 0; + + IF_ENABLED(CONFIG_BT_TBS_CLIENT_TBS, + (tbs_cnt += CONFIG_BT_TBS_CLIENT_MAX_TBS_INSTANCES)); + + tbs_cbs->discover(conn, 0, tbs_cnt, IS_ENABLED(CONFIG_BT_TBS_CLIENT_GTBS)); + } + + return 0; +} diff --git a/tests/bluetooth/audio/mocks/include/conn.h b/tests/bluetooth/audio/mocks/include/conn.h index ae6d3dc70d469..bcf9ca5c8ae81 100644 --- a/tests/bluetooth/audio/mocks/include/conn.h +++ b/tests/bluetooth/audio/mocks/include/conn.h @@ -1,5 +1,6 @@ /* * Copyright (c) 2023 Codecoup + * Copyright (c) 2024 Nordic Semiconductor ASA * * SPDX-License-Identifier: Apache-2.0 */ @@ -7,6 +8,8 @@ #ifndef MOCKS_CONN_H_ #define MOCKS_CONN_H_ +#include + #include struct bt_conn { @@ -15,6 +18,7 @@ struct bt_conn { struct bt_iso_chan *chan; }; +void mock_bt_conn_connected(struct bt_conn *conn, uint8_t err); void mock_bt_conn_disconnected(struct bt_conn *conn, uint8_t err); #endif /* MOCKS_CONN_H_ */ diff --git a/tests/bluetooth/audio/mocks/include/expects_util.h b/tests/bluetooth/audio/mocks/include/expects_util.h index 7cb630b7b33d1..55462dabd6c3a 100644 --- a/tests/bluetooth/audio/mocks/include/expects_util.h +++ b/tests/bluetooth/audio/mocks/include/expects_util.h @@ -7,9 +7,13 @@ #ifndef MOCKS_UTIL_H_ #define MOCKS_UTIL_H_ +#include + #include #include +#include #include +#include #define CHECK_EMPTY(_x) UTIL_BOOL(IS_EMPTY(_x)) #define COND_CODE_EMPTY(_x, _if_any_code, _else_code) \ diff --git a/tests/bluetooth/audio/mocks/src/conn.c b/tests/bluetooth/audio/mocks/src/conn.c index d7570bd0f5f68..0fd9e69888807 100644 --- a/tests/bluetooth/audio/mocks/src/conn.c +++ b/tests/bluetooth/audio/mocks/src/conn.c @@ -1,10 +1,14 @@ /* * Copyright (c) 2023 Codecoup + * Copyright (c) 2024 Nordic Semiconductor ASA * * SPDX-License-Identifier: Apache-2.0 */ +#include + #include +#include #include "conn.h" @@ -27,7 +31,15 @@ struct bt_conn *bt_conn_ref(struct bt_conn *conn) void bt_conn_unref(struct bt_conn *conn) { +} +void mock_bt_conn_connected(struct bt_conn *conn, uint8_t err) +{ + STRUCT_SECTION_FOREACH(bt_conn_cb, cb) { + if (cb->connected) { + cb->connected(conn, err); + } + } } void mock_bt_conn_disconnected(struct bt_conn *conn, uint8_t err) diff --git a/tests/bluetooth/shell/audio.conf b/tests/bluetooth/shell/audio.conf index fbdd2f912d602..f3db71ce56e5a 100644 --- a/tests/bluetooth/shell/audio.conf +++ b/tests/bluetooth/shell/audio.conf @@ -136,6 +136,8 @@ CONFIG_BT_CCP_CALL_CONTROL_SERVER_BEARER_COUNT=2 CONFIG_BT_TBS=y CONFIG_BT_TBS_BEARER_COUNT=1 CONFIG_BT_TBS_SUPPORTED_FEATURES=3 +CONFIG_BT_CCP_CALL_CONTROL_CLIENT=y +CONFIG_BT_CCP_CALL_CONTROL_CLIENT_BEARER_COUNT=2 CONFIG_BT_TBS_CLIENT_TBS=y CONFIG_BT_TBS_CLIENT_GTBS=y diff --git a/tests/bluetooth/shell/testcase.yaml b/tests/bluetooth/shell/testcase.yaml index 6fb5c9db5cdf0..2d45c4a18a875 100644 --- a/tests/bluetooth/shell/testcase.yaml +++ b/tests/bluetooth/shell/testcase.yaml @@ -339,6 +339,7 @@ tests: extra_configs: - CONFIG_BT_TBS_CLIENT_TBS=n - CONFIG_BT_TBS_CLIENT_GTBS=n + - CONFIG_BT_CCP_CALL_CONTROL_CLIENT=n tags: bluetooth bluetooth.shell.audio.tbs_only_client: extra_args: CONF_FILE="audio.conf" @@ -351,6 +352,7 @@ tests: build_only: true extra_configs: - CONFIG_BT_TBS_CLIENT_TBS=n + - CONFIG_BT_CCP_CALL_CONTROL_CLIENT_BEARER_COUNT=1 tags: bluetooth bluetooth.audio_shell.no_cap_acceptor: extra_args: CONF_FILE="audio.conf" diff --git a/tests/bsim/bluetooth/audio/prj.conf b/tests/bsim/bluetooth/audio/prj.conf index 034f3ac0735f8..fe5f431846115 100644 --- a/tests/bsim/bluetooth/audio/prj.conf +++ b/tests/bsim/bluetooth/audio/prj.conf @@ -92,6 +92,8 @@ CONFIG_BT_CSIP_SET_COORDINATOR_TEST_SAMPLE_DATA=y CONFIG_BT_CCP_CALL_CONTROL_SERVER=y CONFIG_BT_TBS=y CONFIG_BT_TBS_BEARER_COUNT=1 + +CONFIG_BT_CCP_CALL_CONTROL_CLIENT=y CONFIG_BT_TBS_CLIENT_TBS=y CONFIG_BT_TBS_CLIENT_GTBS=y CONFIG_BT_TBS_CLIENT_MAX_CALLS=4 diff --git a/tests/bsim/bluetooth/audio/src/ccp_call_control_client_test.c b/tests/bsim/bluetooth/audio/src/ccp_call_control_client_test.c new file mode 100644 index 0000000000000..b848dfc5df2b0 --- /dev/null +++ b/tests/bsim/bluetooth/audio/src/ccp_call_control_client_test.c @@ -0,0 +1,130 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "bstests.h" +#include "common.h" + +#ifdef CONFIG_BT_CCP_CALL_CONTROL_CLIENT +LOG_MODULE_REGISTER(ccp_call_control_client, CONFIG_LOG_DEFAULT_LEVEL); + +extern enum bst_result_t bst_result; + +CREATE_FLAG(flag_discovery_complete); + +static struct bt_ccp_call_control_client *inst; + +static void ccp_call_control_client_discover_cb(struct bt_ccp_call_control_client *client, int err, + struct bt_ccp_call_control_client_bearers *bearers) +{ + if (err != 0) { + FAIL("Failed to discover TBS: %d\n", err); + return; + } + + LOG_INF("Discovery completed with %s%u TBS bearers", + bearers->gtbs_bearer != NULL ? "GTBS and " : "", bearers->tbs_count); + + if (IS_ENABLED(CONFIG_BT_TBS_CLIENT_TBS) && bearers->gtbs_bearer == NULL) { + FAIL("Failed to discover GTBS"); + return; + } + + SET_FLAG(flag_discovery_complete); +} + +static void discover_tbs(void) +{ + int err; + + UNSET_FLAG(flag_discovery_complete); + + err = bt_ccp_call_control_client_discover(default_conn, &inst); + if (err) { + FAIL("Failed to discover TBS: %d", err); + return; + } + + WAIT_FOR_FLAG(flag_discovery_complete); +} + +static void init(void) +{ + static struct bt_ccp_call_control_client_cb ccp_call_control_client_cbs = { + .discover = ccp_call_control_client_discover_cb, + }; + int err; + + err = bt_enable(NULL); + if (err != 0) { + FAIL("Bluetooth discover failed (err %d)\n", err); + return; + } + + err = bt_ccp_call_control_client_register_cb(&ccp_call_control_client_cbs); + if (err != 0) { + FAIL("Failed to register CCP Call Control Client cbs (err %d)\n", err); + return; + } +} + +static void test_main(void) +{ + struct bt_le_ext_adv *ext_adv; + int err; + + init(); + + setup_connectable_adv(&ext_adv); + + printk("Advertising successfully started\n"); + + WAIT_FOR_FLAG(flag_connected); + + discover_tbs(); + discover_tbs(); /* test that we can discover twice */ + + err = bt_conn_disconnect(default_conn, BT_HCI_ERR_REMOTE_USER_TERM_CONN); + if (err != 0) { + FAIL("Failed to disconnect: %d\n", err); + } + + WAIT_FOR_FLAG(flag_disconnected); + + PASS("CCP Call Control Client Passed\n"); +} + +static const struct bst_test_instance test_ccp_call_control_client[] = { + { + .test_id = "ccp_call_control_client", + .test_pre_init_f = test_init, + .test_tick_f = test_tick, + .test_main_f = test_main, + }, + BSTEST_END_MARKER, +}; + +struct bst_test_list *test_ccp_call_control_client_install(struct bst_test_list *tests) +{ + return bst_add_tests(tests, test_ccp_call_control_client); +} + +#else +struct bst_test_list *test_ccp_call_control_client_install(struct bst_test_list *tests) +{ + return tests; +} + +#endif /* CONFIG_BT_CCP_CALL_CONTROL_CLIENT */ diff --git a/tests/bsim/bluetooth/audio/src/ccp_call_control_server_test.c b/tests/bsim/bluetooth/audio/src/ccp_call_control_server_test.c index dfc6ef579531e..ab8771f09276d 100644 --- a/tests/bsim/bluetooth/audio/src/ccp_call_control_server_test.c +++ b/tests/bsim/bluetooth/audio/src/ccp_call_control_server_test.c @@ -118,6 +118,12 @@ static void init(void) LOG_INF("Registered bearer[%d]", i); } + + err = bt_le_scan_start(BT_LE_SCAN_PASSIVE, NULL); + if (err != 0) { + FAIL("Scanning failed to start (err %d)\n", err); + return; + } } static void unregister_bearers(void) @@ -143,6 +149,10 @@ static void test_main(void) { init(); + WAIT_FOR_FLAG(flag_connected); + + WAIT_FOR_FLAG(flag_disconnected); + unregister_bearers(); PASS("CCP Call Control Server Passed\n"); diff --git a/tests/bsim/bluetooth/audio/src/main.c b/tests/bsim/bluetooth/audio/src/main.c index 7fc13176bae9c..3e8161fe7de49 100644 --- a/tests/bsim/bluetooth/audio/src/main.c +++ b/tests/bsim/bluetooth/audio/src/main.c @@ -44,6 +44,7 @@ extern struct bst_test_list *test_csip_notify_client_install(struct bst_test_lis extern struct bst_test_list *test_csip_notify_server_install(struct bst_test_list *tests); extern struct bst_test_list *test_gmap_ugg_install(struct bst_test_list *tests); extern struct bst_test_list *test_gmap_ugt_install(struct bst_test_list *tests); +extern struct bst_test_list *test_ccp_call_control_client_install(struct bst_test_list *tests); extern struct bst_test_list *test_ccp_call_control_server_install(struct bst_test_list *tests); bst_test_install_t test_installers[] = { @@ -83,6 +84,7 @@ bst_test_install_t test_installers[] = { test_csip_notify_server_install, test_gmap_ugg_install, test_gmap_ugt_install, + test_ccp_call_control_client_install, test_ccp_call_control_server_install, NULL, }; diff --git a/tests/bsim/bluetooth/audio/test_scripts/ccp.sh b/tests/bsim/bluetooth/audio/test_scripts/ccp.sh index ed5d1021e4a0b..cbfc3406ee840 100755 --- a/tests/bsim/bluetooth/audio/test_scripts/ccp.sh +++ b/tests/bsim/bluetooth/audio/test_scripts/ccp.sh @@ -13,10 +13,14 @@ cd ${BSIM_OUT_PATH}/bin SIMULATION_ID="ccp" Execute ./bs_${BOARD_TS}_tests_bsim_bluetooth_audio_prj_conf \ - -v=${VERBOSITY_LEVEL} -s=${SIMULATION_ID} -d=0 -testid=ccp_call_control_server -rs=10 -D=1 + -v=${VERBOSITY_LEVEL} -s=${SIMULATION_ID} -d=0 -testid=ccp_call_control_server \ + -RealEncryption=1 -rs=10 -D=2 + +Execute ./bs_${BOARD_TS}_tests_bsim_bluetooth_audio_prj_conf \ + -v=${VERBOSITY_LEVEL} -s=${SIMULATION_ID} -d=1 -testid=ccp_call_control_client \ + -RealEncryption=1 -rs=20 -D=2 # Simulation time should be larger than the WAIT_TIME in common.h -Execute ./bs_2G4_phy_v1 -v=${VERBOSITY_LEVEL} -s=${SIMULATION_ID} \ - -D=1 -sim_length=60e6 $@ +Execute ./bs_2G4_phy_v1 -v=${VERBOSITY_LEVEL} -s=${SIMULATION_ID} -D=2 -sim_length=60e6 $@ wait_for_background_jobs diff --git a/tests/bsim/bluetooth/audio_samples/ccp/call_control_client/CMakeLists.txt b/tests/bsim/bluetooth/audio_samples/ccp/call_control_client/CMakeLists.txt new file mode 100644 index 0000000000000..da0ca3feddbaa --- /dev/null +++ b/tests/bsim/bluetooth/audio_samples/ccp/call_control_client/CMakeLists.txt @@ -0,0 +1,23 @@ +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.20.0) + +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project(ccp_call_control_client_self_tets) + +set(ccp_call_control_client_path ${ZEPHYR_BASE}/samples/bluetooth/ccp_call_control_client) + +target_sources(app PRIVATE + ${ccp_call_control_client_path}/src/main.c +) + +target_sources(app PRIVATE + src/test_main.c +) + +zephyr_library_include_directories(${ZEPHYR_BASE}/samples/bluetooth) + +zephyr_include_directories( + ${BSIM_COMPONENTS_PATH}/libUtilv1/src/ + ${BSIM_COMPONENTS_PATH}/libPhyComv1/src/ + ) diff --git a/tests/bsim/bluetooth/audio_samples/ccp/call_control_client/Kconfig.sysbuild b/tests/bsim/bluetooth/audio_samples/ccp/call_control_client/Kconfig.sysbuild new file mode 100644 index 0000000000000..2fdcdcf687c67 --- /dev/null +++ b/tests/bsim/bluetooth/audio_samples/ccp/call_control_client/Kconfig.sysbuild @@ -0,0 +1,10 @@ +# Copyright (c) 2023-2024 Nordic Semiconductor ASA +# SPDX-License-Identifier: Apache-2.0 + +source "${ZEPHYR_BASE}/samples/bluetooth/ccp_call_control_client/Kconfig.sysbuild" + +config NATIVE_SIMULATOR_PRIMARY_MCU_INDEX + int + # Let's pass the test arguments to the application MCU test + # otherwise by default they would have gone to the net core. + default 0 if $(BOARD_TARGET_STRING) = "NRF5340BSIM_NRF5340_CPUAPP" diff --git a/tests/bsim/bluetooth/audio_samples/ccp/call_control_client/prj.conf b/tests/bsim/bluetooth/audio_samples/ccp/call_control_client/prj.conf new file mode 100644 index 0000000000000..b89931736c1dc --- /dev/null +++ b/tests/bsim/bluetooth/audio_samples/ccp/call_control_client/prj.conf @@ -0,0 +1,2 @@ +# Please build using the sample configuration file: +# ${ZEPHYR_BASE}/samples/bluetooth/ccp_call_control_client/prj.conf diff --git a/tests/bsim/bluetooth/audio_samples/ccp/call_control_client/src/test_main.c b/tests/bsim/bluetooth/audio_samples/ccp/call_control_client/src/test_main.c new file mode 100644 index 0000000000000..84c550b878bce --- /dev/null +++ b/tests/bsim/bluetooth/audio_samples/ccp/call_control_client/src/test_main.c @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include + +#include "bs_types.h" +#include "bs_tracing.h" +#include "bs_utils.h" +#include "bstests.h" + +#define WAIT_TIME 10 /* Seconds */ + +extern enum bst_result_t bst_result; + +#define FAIL(...) \ + do { \ + bst_result = Failed; \ + bs_trace_error_time_line(__VA_ARGS__); \ + } while (0) + +#define PASS(...) \ + do { \ + bst_result = Passed; \ + bs_trace_info_time(1, __VA_ARGS__); \ + } while (0) + +static void test_ccp_call_control_client_sample_init(void) +{ + bst_ticker_set_next_tick_absolute(WAIT_TIME * 1e6); + bst_result = In_progress; +} + +static void test_ccp_call_control_client_sample_tick(bs_time_t HW_device_time) +{ + extern struct bt_ccp_call_control_client *client; + + /* If discovery was a success then client is non-NULL - Use as pass criteria */ + if (client == NULL) { + FAIL("CCP Call Control Client sample FAILED (Did not pass after %i seconds)\n", + WAIT_TIME); + } else { + PASS("CCP Call Control Client sample PASSED\n"); + } +} + +static const struct bst_test_instance test_sample[] = { + { + .test_id = "ccp_call_control_client", + .test_descr = "Test based on the CCP Call Control Client sample. " + "It expects to be connected to a compatible CCP server, " + "waits for " STR(WAIT_TIME) " seconds, and checks how " + "many audio packets have been received correctly", + .test_post_init_f = test_ccp_call_control_client_sample_init, + .test_tick_f = test_ccp_call_control_client_sample_tick, + }, + BSTEST_END_MARKER, +}; + +static struct bst_test_list * +test_ccp_call_control_client_sample_install(struct bst_test_list *tests) +{ + tests = bst_add_tests(tests, test_sample); + return tests; +} + +bst_test_install_t test_installers[] = {test_ccp_call_control_client_sample_install, NULL}; diff --git a/tests/bsim/bluetooth/audio_samples/ccp/call_control_client/sysbuild.cmake b/tests/bsim/bluetooth/audio_samples/ccp/call_control_client/sysbuild.cmake new file mode 100644 index 0000000000000..3af3a8a382402 --- /dev/null +++ b/tests/bsim/bluetooth/audio_samples/ccp/call_control_client/sysbuild.cmake @@ -0,0 +1,6 @@ +# Copyright (c) 2023-2024 Nordic Semiconductor ASA +# SPDX-License-Identifier: Apache-2.0 + +include(${ZEPHYR_BASE}/samples/bluetooth/ccp_call_control_client/sysbuild.cmake) + +native_simulator_set_primary_mcu_index(${DEFAULT_IMAGE} ${NET_APP}) diff --git a/tests/bsim/bluetooth/audio_samples/ccp/compile.sh b/tests/bsim/bluetooth/audio_samples/ccp/compile.sh index 186805e7ec416..cfc92e66c788e 100755 --- a/tests/bsim/bluetooth/audio_samples/ccp/compile.sh +++ b/tests/bsim/bluetooth/audio_samples/ccp/compile.sh @@ -15,12 +15,22 @@ if [ "${BOARD_TS}" == "nrf5340bsim_nrf5340_cpuapp" ]; then conf_file=${sample}/prj.conf \ conf_overlay=${sample}/boards/nrf5340_audio_dk_nrf5340_cpuapp.conf \ exe_name=bs_${BOARD_TS}_${app}_prj_conf sysbuild=1 compile + app=tests/bsim/bluetooth/audio_samples/ccp/call_control_client \ + sample=${ZEPHYR_BASE}/samples/bluetooth/ccp_call_control_client \ + conf_file=${sample}/prj.conf \ + conf_overlay=${sample}/boards/nrf5340_audio_dk_nrf5340_cpuapp.conf \ + exe_name=bs_${BOARD_TS}_${app}_prj_conf sysbuild=1 compile else app=tests/bsim/bluetooth/audio_samples/ccp/call_control_server \ sample=${ZEPHYR_BASE}/samples/bluetooth/ccp_call_control_server \ conf_file=${sample}/prj.conf \ conf_overlay=${sample}/overlay-bt_ll_sw_split.conf \ exe_name=bs_${BOARD_TS}_${app}_prj_conf sysbuild=1 compile + app=tests/bsim/bluetooth/audio_samples/ccp/call_control_client \ + sample=${ZEPHYR_BASE}/samples/bluetooth/ccp_call_control_client \ + conf_file=${sample}/prj.conf \ + conf_overlay=${sample}/overlay-bt_ll_sw_split.conf \ + exe_name=bs_${BOARD_TS}_${app}_prj_conf sysbuild=1 compile fi wait_for_background_jobs diff --git a/tests/bsim/bluetooth/audio_samples/ccp/tests_scripts/ccp.sh b/tests/bsim/bluetooth/audio_samples/ccp/tests_scripts/ccp.sh index 87564607df7ab..e366ea54eee11 100755 --- a/tests/bsim/bluetooth/audio_samples/ccp/tests_scripts/ccp.sh +++ b/tests/bsim/bluetooth/audio_samples/ccp/tests_scripts/ccp.sh @@ -14,7 +14,10 @@ cd ${BSIM_OUT_PATH}/bin Execute ./bs_${BOARD_TS}_tests_bsim_bluetooth_audio_samples_ccp_call_control_server_prj_conf \ -v=${verbosity_level} -s=${simulation_id} -d=0 -RealEncryption=1 -testid=ccp_call_control_server +Execute ./bs_${BOARD_TS}_tests_bsim_bluetooth_audio_samples_ccp_call_control_client_prj_conf \ + -v=${verbosity_level} -s=${simulation_id} -d=1 -RealEncryption=1 -testid=ccp_call_control_client + Execute ./bs_2G4_phy_v1 -v=${verbosity_level} -s=${simulation_id} \ - -D=1 -sim_length=20e6 $@ -argschannel -at=40 + -D=2 -sim_length=20e6 $@ -argschannel -at=40 wait_for_background_jobs #Wait for all programs in background and return != 0 if any fails