Bluetooth: CAP: Commander discovery support

Implement the CAP Commander discovery function.

Adds support for it in the shell.

This includes initial babblesim and unit testing as well.

Signed-off-by: Emil Gydesen <emil.gydesen@nordicsemi.no>
This commit is contained in:
Emil Gydesen 2023-11-06 13:19:00 +01:00 committed by Carles Cufí
commit cda5e58aa5
25 changed files with 917 additions and 12 deletions

View file

@ -137,3 +137,40 @@ used.
uart:~$ cap_initiator unicast-stop
Unicast stopped for group 0x81e41c0 completed
CAP Commander
*************
The Commander will typically be a either co-located with a CAP Initiator or be on a separate
resource-rich mobile device, such as a phone or smartwatch. The Commander can
discover CAP Acceptors's CAS and optional CSIS services. The CSIS service can be read to provide
information about other CAP Acceptors in the same Coordinated Set. The Commander can provide
information about broadcast sources to CAP Acceptors or coordinate capture and rendering information
such as mute or volume states.
Using the CAP Commander
=======================
When the Bluetooth stack has been initialized (:code:`bt init`), the Commander can discover CAS and
the optionally included CSIS instance by calling (:code:`cap_commander discover`).
.. code-block:: console
cap_commander --help
cap_commander - Bluetooth CAP commander shell commands
Subcommands:
discover :Discover CAS
Before being able to perform any stream operation, the device must also perform the
:code:`bap discover` operation to discover the ASEs and PAC records. The :code:`bap init`
command also needs to be called.
When connected
--------------
Discovering CAS and CSIS on a device:
.. code-block:: console
uart:~$ cap_commander discover
discovery completed with CSIS

View file

@ -131,7 +131,10 @@ struct bt_cap_initiator_cb {
*
* @param conn Connection to a remote server.
*
* @return 0 on success or negative error value on failure.
* @retval 0 Success
* @retval -EINVAL @p conn is NULL
* @retval -ENOTCONN @p conn is not connected
* @retval -ENOMEM Could not allocated memory for the request
*/
int bt_cap_initiator_unicast_discover(struct bt_conn *conn);
@ -250,7 +253,7 @@ struct bt_cap_unicast_audio_update_param {
};
/**
* @brief Register Common Audio Profile callbacks
* @brief Register Common Audio Profile Initiator callbacks
*
* @param cb The callback structure. Shall remain static.
*
@ -636,6 +639,45 @@ struct bt_cap_broadcast_to_unicast_param {
int bt_cap_initiator_broadcast_to_unicast(const struct bt_cap_broadcast_to_unicast_param *param,
struct bt_bap_unicast_group **unicast_group);
/** Callback structure for CAP procedures */
struct bt_cap_commander_cb {
/**
* @brief Callback for bt_cap_initiator_unicast_discover().
*
* @param conn The connection pointer supplied to
* bt_cap_initiator_unicast_discover().
* @param err 0 if Common Audio Service was found else -ENODATA.
* @param csis_inst The Coordinated Set Identification Service if
* Common Audio Service was found and includes a
* Coordinated Set Identification Service.
* NULL on error or if remote device does not include
* Coordinated Set Identification Service.
*/
void (*discovery_complete)(struct bt_conn *conn, int err,
const struct bt_csip_set_coordinator_csis_inst *csis_inst);
};
/**
* @brief Register Common Audio Profile Commander callbacks
*
* @param cb The callback structure. Shall remain static.
*
* @retval 0 Success
* @retval -EINVAL @p cb is NULL
* @retval -EALREADY Callbacks are already registered
*/
int bt_cap_commander_register_cb(const struct bt_cap_commander_cb *cb);
/**
* @brief Unregister Common Audio Profile Commander callbacks
*
* @param cb The callback structure that was previously registered.
*
* @retval 0 Success
* @retval -EINVAL @p cb is NULL or @p cb was not registered
*/
int bt_cap_commander_unregister_cb(const struct bt_cap_commander_cb *cb);
/**
* @brief Discovers audio support on a remote device.
*
@ -644,13 +686,17 @@ int bt_cap_initiator_broadcast_to_unicast(const struct bt_cap_broadcast_to_unica
*
* @note @kconfig{CONFIG_BT_CAP_COMMANDER} must be enabled for this function. If
* @kconfig{CONFIG_BT_CAP_INITIATOR} is also enabled, it does not matter if
* bt_cap_commander_unicast_discover() or bt_cap_initiator_unicast_discover() is used.
* bt_cap_commander_discover() or bt_cap_initiator_unicast_discover() is used.
*
* @param conn Connection to a remote server.
*
* @return 0 on success or negative error value on failure.
* @retval 0 Success
* @retval -EINVAL @p conn is NULL
* @retval -ENOTCONN @p conn is not connected
* @retval -ENOMEM Could not allocated memory for the request
* @retval -EBUSY Already doing discovery for @p conn
*/
int bt_cap_commander_unicast_discover(struct bt_conn *conn);
int bt_cap_commander_discover(struct bt_conn *conn);
struct bt_cap_commander_broadcast_reception_start_member_param {
/** Coordinated or ad-hoc set member. */

View file

@ -20,9 +20,59 @@ LOG_MODULE_REGISTER(bt_cap_commander, CONFIG_BT_CAP_COMMANDER_LOG_LEVEL);
#include "common/bt_str.h"
int bt_cap_commander_unicast_discover(struct bt_conn *conn)
static const struct bt_cap_commander_cb *cap_cb;
int bt_cap_commander_register_cb(const struct bt_cap_commander_cb *cb)
{
return -ENOSYS;
CHECKIF(cb == NULL) {
LOG_DBG("cb is NULL");
return -EINVAL;
}
CHECKIF(cap_cb != NULL) {
LOG_DBG("callbacks already registered");
return -EALREADY;
}
cap_cb = cb;
return 0;
}
int bt_cap_commander_unregister_cb(const struct bt_cap_commander_cb *cb)
{
CHECKIF(cb == NULL) {
LOG_DBG("cb is NULL");
return -EINVAL;
}
CHECKIF(cap_cb != cb) {
LOG_DBG("cb is not registered");
return -EINVAL;
}
cap_cb = NULL;
return 0;
}
static void
cap_commander_discover_complete(struct bt_conn *conn, int err,
const struct bt_csip_set_coordinator_csis_inst *csis_inst)
{
if (cap_cb && cap_cb->discovery_complete) {
cap_cb->discovery_complete(conn, err, csis_inst);
}
}
int bt_cap_commander_discover(struct bt_conn *conn)
{
CHECKIF(conn == NULL) {
LOG_DBG("NULL conn");
return -EINVAL;
}
return bt_cap_common_discover(conn, cap_commander_discover_complete);
}
int bt_cap_commander_broadcast_reception_start(

View file

@ -240,7 +240,6 @@ static uint8_t bt_cap_common_discover_included_cb(struct bt_conn *conn,
client->csis_start_handle = included_service->start_handle;
client->csis_inst = bt_csip_set_coordinator_csis_inst_by_handle(
conn, client->csis_start_handle);
if (client->csis_inst == NULL) {
static struct bt_csip_set_coordinator_cb csis_client_cb = {
.discover = csis_client_discover_cb,
@ -325,10 +324,20 @@ int bt_cap_common_discover(struct bt_conn *conn, bt_cap_common_discover_func_t f
param->start_handle = BT_ATT_FIRST_ATTRIBUTE_HANDLE;
param->end_handle = BT_ATT_LAST_ATTRIBUTE_HANDLE;
discover_cb_func = func;
err = bt_gatt_discover(conn, param);
if (err == 0) {
discover_cb_func = func;
if (err != 0) {
discover_cb_func = NULL;
/* Report expected possible errors */
if (err == -ENOTCONN || err == -ENOMEM) {
return err;
}
LOG_DBG("Unexpected err %d from bt_gatt_discover", err);
return -ENOEXEC;
}
return err;
return 0;
}

View file

@ -10,7 +10,6 @@
#ifndef ZEPHYR_INCLUDE_BLUETOOTH_CCID_H_
#define ZEPHYR_INCLUDE_BLUETOOTH_CCID_H_
#include <zephyr/device.h>
#include <zephyr/types.h>
/**

View file

@ -59,6 +59,10 @@ zephyr_library_sources_ifdef(
CONFIG_BT_CAP_INITIATOR
cap_initiator.c
)
zephyr_library_sources_ifdef(
CONFIG_BT_CAP_COMMANDER
cap_commander.c
)
zephyr_library_sources_ifdef(
CONFIG_BT_HAS_CLIENT
has_client.c

View file

@ -0,0 +1,79 @@
/**
* @file
* @brief Shell APIs for Bluetooth CAP commander
*
* Copyright (c) 2023 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdlib.h>
#include <zephyr/types.h>
#include <zephyr/shell/shell.h>
#include <zephyr/bluetooth/conn.h>
#include <zephyr/bluetooth/audio/cap.h>
#include "shell/bt.h"
#include "audio.h"
static void cap_discover_cb(struct bt_conn *conn, int err,
const struct bt_csip_set_coordinator_csis_inst *csis_inst)
{
if (err != 0) {
shell_error(ctx_shell, "discover failed (%d)", err);
return;
}
shell_print(ctx_shell, "discovery completed%s", csis_inst == NULL ? "" : " with CSIS");
}
static struct bt_cap_commander_cb cbs = {
.discovery_complete = cap_discover_cb,
};
static int cmd_cap_commander_discover(const struct shell *sh, size_t argc, char *argv[])
{
static bool cbs_registered;
int err;
if (default_conn == NULL) {
shell_error(sh, "Not connected");
return -ENOEXEC;
}
if (ctx_shell == NULL) {
ctx_shell = sh;
}
if (!cbs_registered) {
bt_cap_commander_register_cb(&cbs);
cbs_registered = true;
}
err = bt_cap_commander_discover(default_conn);
if (err != 0) {
shell_error(sh, "Fail: %d", err);
}
return err;
}
static int cmd_cap_commander(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(
cap_commander_cmds,
SHELL_CMD_ARG(discover, NULL, "Discover CAS", cmd_cap_commander_discover, 1, 0),
SHELL_SUBCMD_SET_END
);
SHELL_CMD_ARG_REGISTER(cap_commander, &cap_commander_cmds, "Bluetooth CAP commander shell commands",
cmd_cap_commander, 1, 1);

View file

@ -0,0 +1,18 @@
# SPDX-License-Identifier: Apache-2.0
cmake_minimum_required(VERSION 3.20.0)
project(bluetooth_ascs)
find_package(Zephyr COMPONENTS unittest HINTS $ENV{ZEPHYR_BASE})
add_subdirectory(${ZEPHYR_BASE}/tests/bluetooth/audio/cap_commander/uut uut)
target_link_libraries(testbinary PRIVATE uut)
target_include_directories(testbinary PRIVATE include)
target_sources(testbinary
PRIVATE
${ZEPHYR_BASE}/subsys/bluetooth/host/uuid.c
src/main.c
)

View file

@ -0,0 +1,18 @@
CONFIG_ZTEST=y
CONFIG_BT=y
CONFIG_BT_CENTRAL=y
CONFIG_BT_AUDIO=y
# Requirements for CAP commander
CONFIG_BT_VCP_VOL_CTLR=y
CONFIG_BT_CSIP_SET_COORDINATOR=y
CONFIG_BT_CAP_COMMANDER=y
CONFIG_LOG=y
CONFIG_BT_CAP_COMMANDER_LOG_LEVEL_DBG=y
CONFIG_ASSERT=y
CONFIG_ASSERT_LEVEL=2
CONFIG_ASSERT_VERBOSE=y

View file

@ -0,0 +1,166 @@
/* main.c - Application main entry point */
/*
* Copyright (c) 2023 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdlib.h>
#include <zephyr/bluetooth/audio/cap.h>
#include <zephyr/fff.h>
#include "bluetooth.h"
#include "cap_commander.h"
#include "conn.h"
#include "expects_util.h"
DEFINE_FFF_GLOBALS;
static void mock_init_rule_before(const struct ztest_unit_test *test, void *fixture)
{
mock_cap_commander_init();
}
static void mock_destroy_rule_after(const struct ztest_unit_test *test, void *fixture)
{
mock_cap_commander_cleanup();
}
ZTEST_RULE(mock_rule, mock_init_rule_before, mock_destroy_rule_after);
struct cap_commander_test_suite_fixture {
struct bt_conn conn;
};
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_PERIPHERAL;
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;
}
static void cap_commander_test_suite_fixture_init(struct cap_commander_test_suite_fixture *fixture)
{
test_conn_init(&fixture->conn);
}
static void *cap_commander_test_suite_setup(void)
{
struct cap_commander_test_suite_fixture *fixture;
fixture = malloc(sizeof(*fixture));
zassert_not_null(fixture);
return fixture;
}
static void cap_commander_test_suite_before(void *f)
{
memset(f, 0, sizeof(struct cap_commander_test_suite_fixture));
cap_commander_test_suite_fixture_init(f);
}
static void cap_commander_test_suite_after(void *f)
{
bt_cap_commander_unregister_cb(&mock_cap_commander_cb);
}
static void cap_commander_test_suite_teardown(void *f)
{
free(f);
}
ZTEST_SUITE(cap_commander_test_suite, NULL, cap_commander_test_suite_setup,
cap_commander_test_suite_before, cap_commander_test_suite_after,
cap_commander_test_suite_teardown);
ZTEST_F(cap_commander_test_suite, test_commander_register_cb)
{
int err;
err = bt_cap_commander_register_cb(&mock_cap_commander_cb);
zassert_equal(0, err, "Unexpected return value %d", err);
}
ZTEST_F(cap_commander_test_suite, test_commander_register_cb_inval_param_null)
{
int err;
err = bt_cap_commander_register_cb(NULL);
zassert_equal(-EINVAL, err, "Unexpected return value %d", err);
}
ZTEST_F(cap_commander_test_suite, test_commander_register_cb_inval_double_register)
{
int err;
err = bt_cap_commander_register_cb(&mock_cap_commander_cb);
zassert_equal(0, err, "Unexpected return value %d", err);
err = bt_cap_commander_register_cb(&mock_cap_commander_cb);
zassert_equal(-EALREADY, err, "Unexpected return value %d", err);
}
ZTEST_F(cap_commander_test_suite, test_commander_unregister_cb)
{
int err;
err = bt_cap_commander_register_cb(&mock_cap_commander_cb);
zassert_equal(0, err, "Unexpected return value %d", err);
err = bt_cap_commander_unregister_cb(&mock_cap_commander_cb);
zassert_equal(0, err, "Unexpected return value %d", err);
}
ZTEST_F(cap_commander_test_suite, test_commander_unregister_cb_inval_param_null)
{
int err;
err = bt_cap_commander_unregister_cb(NULL);
zassert_equal(-EINVAL, err, "Unexpected return value %d", err);
}
ZTEST_F(cap_commander_test_suite, test_commander_unregister_cb_inval_double_unregister)
{
int err;
err = bt_cap_commander_register_cb(&mock_cap_commander_cb);
zassert_equal(0, err, "Unexpected return value %d", err);
err = bt_cap_commander_unregister_cb(&mock_cap_commander_cb);
zassert_equal(0, err, "Unexpected return value %d", err);
err = bt_cap_commander_unregister_cb(&mock_cap_commander_cb);
zassert_equal(-EINVAL, err, "Unexpected return value %d", err);
}
ZTEST_F(cap_commander_test_suite, test_commander_discover)
{
int err;
err = bt_cap_commander_register_cb(&mock_cap_commander_cb);
zassert_equal(0, err, "Unexpected return value %d", err);
err = bt_cap_commander_discover(&fixture->conn);
zassert_equal(0, err, "Unexpected return value %d", err);
zexpect_call_count("bt_cap_commander_cb.discovery_complete", 1,
mock_cap_commander_discovery_complete_cb_fake.call_count);
}
ZTEST_F(cap_commander_test_suite, test_commander_discover_inval_param_null)
{
int err;
err = bt_cap_commander_register_cb(&mock_cap_commander_cb);
zassert_equal(0, err, "Unexpected return value %d", err);
err = bt_cap_commander_discover(NULL);
zassert_equal(-EINVAL, err, "Unexpected return value %d", err);
}

View file

@ -0,0 +1,7 @@
common:
tags:
- bluetooth
- bluetooth_audio
tests:
bluetooth.audio.cap_commander.test_default:
type: unit

View file

@ -0,0 +1,21 @@
#
# Copyright (c) 2023 Nordic Semiconductor ASA
#
# SPDX-License-Identifier: Apache-2.0
#
# CMakeLists.txt file for creating of uut library.
#
add_library(uut STATIC
${ZEPHYR_BASE}/subsys/bluetooth/audio/cap_commander.c
${ZEPHYR_BASE}/subsys/bluetooth/audio/cap_common.c
${ZEPHYR_BASE}/subsys/logging/log_minimal.c
${ZEPHYR_BASE}/subsys/net/buf_simple.c
csip.c
)
add_subdirectory(${ZEPHYR_BASE}/tests/bluetooth/audio/mocks mocks)
target_link_libraries(uut PUBLIC test_interface mocks)
target_compile_options(uut PRIVATE -std=c11 -include ztest.h)

View file

@ -0,0 +1,62 @@
/* csip.c - CAP Commander specific CSIP mocks */
/*
* Copyright (c) 2023 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/bluetooth/audio/csip.h>
static struct bt_csip_set_coordinator_cb *csip_cb;
struct bt_csip_set_coordinator_svc_inst {
struct bt_conn *conn;
struct bt_csip_set_coordinator_set_info *set_info;
} svc_inst;
static struct bt_csip_set_coordinator_set_member member = {
.insts = {
{
.info = {
.set_size = 2,
.rank = 1,
.lockable = false,
},
.svc_inst = &svc_inst,
},
},
};
struct bt_csip_set_coordinator_csis_inst *
bt_csip_set_coordinator_csis_inst_by_handle(struct bt_conn *conn, uint16_t start_handle)
{
return &member.insts[0];
}
int bt_csip_set_coordinator_register_cb(struct bt_csip_set_coordinator_cb *cb)
{
csip_cb = cb;
return 0;
}
int bt_csip_set_coordinator_discover(struct bt_conn *conn)
{
if (csip_cb != NULL) {
svc_inst.conn = conn;
svc_inst.set_info = &member.insts[0].info;
csip_cb->discover(conn, &member, 0, 1);
}
return 0;
}
void mock_bt_csip_init(void)
{
csip_cb = NULL;
}
void mock_bt_csip_cleanup(void)
{
}

View file

@ -10,6 +10,7 @@ add_library(mocks STATIC
src/bap_stream.c
src/bap_unicast_client.c
src/bap_unicast_server.c
src/cap_commander.c
src/conn.c
src/crypto.c
src/fatal.c

View file

@ -0,0 +1,21 @@
/*
* Copyright (c) 2023 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef MOCKS_CAP_COMMANDER_H_
#define MOCKS_CAP_COMMANDER_H_
#include <zephyr/fff.h>
#include <zephyr/bluetooth/audio/cap.h>
extern struct bt_cap_commander_cb mock_cap_commander_cb;
void mock_cap_commander_init(void);
void mock_cap_commander_cleanup(void);
DECLARE_FAKE_VOID_FUNC(mock_cap_commander_discovery_complete_cb, struct bt_conn *, int,
const struct bt_csip_set_coordinator_csis_inst *);
#endif /* MOCKS_CAP_COMMANDER_H_ */

View file

@ -0,0 +1,28 @@
/*
* Copyright (c) 2023 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/bluetooth/audio/cap.h>
#include "cap_commander.h"
/* List of fakes used by this unit tester */
#define FFF_FAKES_LIST(FAKE) FAKE(mock_cap_commander_discovery_complete_cb)
struct bt_cap_commander_cb mock_cap_commander_cb;
DEFINE_FAKE_VOID_FUNC(mock_cap_commander_discovery_complete_cb, struct bt_conn *, int,
const struct bt_csip_set_coordinator_csis_inst *);
void mock_cap_commander_init(void)
{
FFF_FAKES_LIST(RESET_FAKE);
mock_cap_commander_cb.discovery_complete = mock_cap_commander_discovery_complete_cb;
}
void mock_cap_commander_cleanup(void)
{
}

View file

@ -7,9 +7,12 @@
#include <stdlib.h>
#include <zephyr/types.h>
#include <zephyr/bluetooth/gatt.h>
#include <zephyr/bluetooth/att.h>
#include <zephyr/bluetooth/uuid.h>
#include <zephyr/sys/iterable_sections.h>
#include "gatt.h"
#include "conn.h"
#define LOG_LEVEL CONFIG_BT_GATT_LOG_LEVEL
#include <zephyr/logging/log.h>
@ -283,6 +286,63 @@ ssize_t bt_gatt_attr_read(struct bt_conn *conn, const struct bt_gatt_attr *attr,
return len;
}
int bt_gatt_discover(struct bt_conn *conn, struct bt_gatt_discover_params *params)
{
zassert_not_null(conn, "'%s()' was called with incorrect '%s' value", __func__, "conn");
zassert_not_null(params, "'%s()' was called with incorrect '%s' value", __func__, "params");
zassert_not_null(params->func, "'%s()' was called with incorrect '%s' value", __func__,
"params->func");
zassert_between_inclusive(
params->start_handle, BT_ATT_FIRST_ATTRIBUTE_HANDLE, BT_ATT_LAST_ATTRIBUTE_HANDLE,
"'%s()' was called with incorrect '%s' value", __func__, "params->start_handle");
zassert_between_inclusive(
params->end_handle, BT_ATT_FIRST_ATTRIBUTE_HANDLE, BT_ATT_LAST_ATTRIBUTE_HANDLE,
"'%s()' was called with incorrect '%s' value", __func__, "params->end_handle");
zassert_true(params->start_handle <= params->end_handle,
"'%s()' was called with incorrect '%s' value", __func__, "params->end_handle");
struct bt_gatt_service_val value;
struct bt_uuid_16 uuid;
struct bt_gatt_attr attr;
uint16_t start_handle;
uint16_t end_handle;
if (conn->info.state != BT_CONN_STATE_CONNECTED) {
return -ENOTCONN;
}
switch (params->type) {
case BT_GATT_DISCOVER_PRIMARY:
case BT_GATT_DISCOVER_SECONDARY:
case BT_GATT_DISCOVER_STD_CHAR_DESC:
case BT_GATT_DISCOVER_INCLUDE:
case BT_GATT_DISCOVER_CHARACTERISTIC:
case BT_GATT_DISCOVER_DESCRIPTOR:
case BT_GATT_DISCOVER_ATTRIBUTE:
break;
default:
LOG_ERR("Invalid discovery type: %u", params->type);
return -EINVAL;
}
uuid.uuid.type = BT_UUID_TYPE_16;
uuid.val = params->type;
start_handle = params->start_handle;
end_handle = params->end_handle;
value.end_handle = end_handle;
value.uuid = params->uuid;
attr = (struct bt_gatt_attr){
.uuid = &uuid.uuid,
.user_data = &value,
.handle = start_handle,
};
params->func(conn, &attr, params);
return 0;
}
uint16_t bt_gatt_get_mtu(struct bt_conn *conn)
{
return 64;

View file

@ -309,3 +309,9 @@ tests:
platform_allow: native_posix
extra_configs:
- CONFIG_BT_GMAP=n
bluetooth.audio_shell.no_cap_commander:
extra_args: CONF_FILE="audio.conf"
build_only: true
platform_allow: native_posix
extra_configs:
- CONFIG_BT_CAP_COMMANDER=n

View file

@ -124,6 +124,7 @@ CONFIG_BT_HAS_FEATURES_NOTIFIABLE=y
# Common Audio Profile
CONFIG_BT_CAP_ACCEPTOR=y
CONFIG_BT_CAP_ACCEPTOR_SET_MEMBER=y
CONFIG_BT_CAP_COMMANDER=y
CONFIG_BT_CAP_INITIATOR=y
# Telephony and Media Audio Profile

View file

@ -758,6 +758,15 @@ static void test_cap_acceptor_broadcast(void)
PASS("CAP acceptor broadcast passed\n");
}
static void test_cap_acceptor_capture_and_render(void)
{
init();
WAIT_FOR_FLAG(flag_connected);
PASS("CAP acceptor unicast passed\n");
}
static const struct bst_test_instance test_cap_acceptor[] = {
{
.test_id = "cap_acceptor_unicast",
@ -777,6 +786,12 @@ static const struct bst_test_instance test_cap_acceptor[] = {
.test_tick_f = test_tick,
.test_main_f = test_cap_acceptor_broadcast,
},
{
.test_id = "cap_acceptor_capture_and_render",
.test_post_init_f = test_init,
.test_tick_f = test_tick,
.test_main_f = test_cap_acceptor_capture_and_render,
},
BSTEST_END_MARKER,
};

View file

@ -0,0 +1,216 @@
/*
* Copyright (c) 2023 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#if defined(CONFIG_BT_CAP_COMMANDER)
#include <zephyr/bluetooth/bluetooth.h>
#include <zephyr/bluetooth/byteorder.h>
#include <zephyr/bluetooth/audio/bap_lc3_preset.h>
#include <zephyr/bluetooth/audio/cap.h>
#include <zephyr/bluetooth/audio/bap.h>
#include <zephyr/sys/byteorder.h>
#include "common.h"
#include "bap_common.h"
extern enum bst_result_t bst_result;
static struct bt_conn *connected_conns[CONFIG_BT_MAX_CONN];
static volatile size_t connected_conn_cnt;
CREATE_FLAG(flag_discovered);
CREATE_FLAG(flag_mtu_exchanged);
static void cap_discovery_complete_cb(struct bt_conn *conn, int err,
const struct bt_csip_set_coordinator_csis_inst *csis_inst)
{
if (err != 0) {
FAIL("Failed to discover CAS: %d", err);
return;
}
if (IS_ENABLED(CONFIG_BT_CAP_ACCEPTOR_SET_MEMBER)) {
if (csis_inst == NULL) {
FAIL("Failed to discover CAS CSIS");
return;
}
printk("Found CAS with CSIS %p\n", csis_inst);
} else {
printk("Found CAS\n");
}
SET_FLAG(flag_discovered);
}
static struct bt_cap_commander_cb cap_cb = {
.discovery_complete = cap_discovery_complete_cb,
};
static void att_mtu_updated(struct bt_conn *conn, uint16_t tx, uint16_t rx)
{
printk("MTU exchanged\n");
SET_FLAG(flag_mtu_exchanged);
}
static struct bt_gatt_cb gatt_callbacks = {
.att_mtu_updated = att_mtu_updated,
};
static void init(void)
{
int err;
err = bt_enable(NULL);
if (err != 0) {
FAIL("Bluetooth enable failed (err %d)\n", err);
return;
}
bt_gatt_cb_register(&gatt_callbacks);
err = bt_cap_commander_register_cb(&cap_cb);
if (err != 0) {
FAIL("Failed to register CAP callbacks (err %d)\n", err);
return;
}
}
static void cap_device_found(const bt_addr_le_t *addr, int8_t rssi, uint8_t type,
struct net_buf_simple *ad)
{
char addr_str[BT_ADDR_LE_STR_LEN];
struct bt_conn *conn;
int err;
/* We're only interested in connectable events */
if (type != BT_HCI_ADV_IND && type != BT_HCI_ADV_DIRECT_IND) {
return;
}
conn = bt_conn_lookup_addr_le(BT_ID_DEFAULT, addr);
if (conn != NULL) {
/* Already connected to this device */
bt_conn_unref(conn);
return;
}
bt_addr_le_to_str(addr, addr_str, sizeof(addr_str));
printk("Device found: %s (RSSI %d)\n", addr_str, rssi);
/* connect only to devices in close proximity */
if (rssi < -70) {
FAIL("RSSI too low");
return;
}
printk("Stopping scan\n");
if (bt_le_scan_stop()) {
FAIL("Could not stop scan");
return;
}
err = bt_conn_le_create(
addr, BT_CONN_LE_CREATE_CONN,
BT_LE_CONN_PARAM(BT_GAP_INIT_CONN_INT_MIN, BT_GAP_INIT_CONN_INT_MIN, 0, 400),
&connected_conns[connected_conn_cnt]);
if (err) {
FAIL("Could not connect to peer: %d", err);
}
}
static void scan_and_connect(void)
{
int err;
UNSET_FLAG(flag_connected);
err = bt_le_scan_start(BT_LE_SCAN_PASSIVE, cap_device_found);
if (err != 0) {
FAIL("Scanning failed to start (err %d)\n", err);
return;
}
printk("Scanning successfully started\n");
WAIT_FOR_FLAG(flag_connected);
connected_conn_cnt++;
}
static void disconnect_acl(struct bt_conn *conn)
{
int err;
err = bt_conn_disconnect(conn, BT_HCI_ERR_REMOTE_USER_TERM_CONN);
if (err != 0) {
FAIL("Failed to disconnect (err %d)\n", err);
return;
}
}
static void discover_cas(struct bt_conn *conn)
{
int err;
UNSET_FLAG(flag_discovered);
err = bt_cap_commander_discover(conn);
if (err != 0) {
printk("Failed to discover CAS: %d\n", err);
return;
}
WAIT_FOR_FLAG(flag_discovered);
}
static void test_main_cap_commander_capture_and_render(void)
{
init();
/* Connect to and do discovery on all CAP acceptors */
for (size_t i = 0U; i < get_dev_cnt() - 1; i++) {
scan_and_connect();
WAIT_FOR_FLAG(flag_mtu_exchanged);
/* TODO: We should use CSIP to find set members */
discover_cas(default_conn);
}
/* TODO: Do CAP Commander stuff */
/* Disconnect all CAP acceptors */
for (size_t i = 0U; i < connected_conn_cnt; i++) {
disconnect_acl(connected_conns[i]);
}
connected_conn_cnt = 0U;
PASS("CAP commander capture and rendering passed\n");
}
static const struct bst_test_instance test_cap_commander[] = {
{
.test_id = "cap_commander_capture_and_render",
.test_post_init_f = test_init,
.test_tick_f = test_tick,
.test_main_f = test_main_cap_commander_capture_and_render,
},
BSTEST_END_MARKER,
};
struct bst_test_list *test_cap_commander_install(struct bst_test_list *tests)
{
return bst_add_tests(tests, test_cap_commander);
}
#else /* !CONFIG_BT_CAP_COMMANDER */
struct bst_test_list *test_cap_commander_install(struct bst_test_list *tests)
{
return tests;
}
#endif /* CONFIG_BT_CAP_COMMANDER */

View file

@ -167,6 +167,11 @@ static void register_more_cmd_args(void)
}
NATIVE_TASK(register_more_cmd_args, PRE_BOOT_1, 100);
uint16_t get_dev_cnt(void)
{
return (uint16_t)dev_cnt;
}
/**
* @brief Get the channel id based on remote device ID
*

View file

@ -98,6 +98,7 @@ extern volatile bt_security_t security_level;
void disconnected(struct bt_conn *conn, uint8_t reason);
void test_tick(bs_time_t HW_device_time);
void test_init(void);
uint16_t get_dev_cnt(void);
void backchannel_sync_send(uint dev);
void backchannel_sync_send_all(void);
void backchannel_sync_wait(uint dev);

View file

@ -25,6 +25,7 @@ extern struct bst_test_list *test_scan_delegator_install(struct bst_test_list *t
extern struct bst_test_list *test_bap_broadcast_assistant_install(struct bst_test_list *tests);
extern struct bst_test_list *test_bass_broadcaster_install(struct bst_test_list *tests);
extern struct bst_test_list *test_cap_acceptor_install(struct bst_test_list *tests);
extern struct bst_test_list *test_cap_commander_install(struct bst_test_list *tests);
extern struct bst_test_list *test_cap_initiator_broadcast_install(struct bst_test_list *tests);
extern struct bst_test_list *test_cap_initiator_unicast_install(struct bst_test_list *tests);
extern struct bst_test_list *test_has_install(struct bst_test_list *tests);
@ -59,6 +60,7 @@ bst_test_install_t test_installers[] = {
test_scan_delegator_install,
test_bap_broadcast_assistant_install,
test_bass_broadcaster_install,
test_cap_commander_install,
test_cap_acceptor_install,
test_cap_initiator_broadcast_install,
test_cap_initiator_unicast_install,

View file

@ -0,0 +1,33 @@
#!/usr/bin/env bash
#
# Copyright (c) 2023 Nordic Semiconductor ASA
#
# SPDX-License-Identifier: Apache-2.0
SIMULATION_ID="cap_capture_and_render"
VERBOSITY_LEVEL=2
EXECUTE_TIMEOUT=20
source ${ZEPHYR_BASE}/tests/bsim/sh_common.source
cd ${BSIM_OUT_PATH}/bin
printf "\n\n======== Running CAP commander capture and rendering test =========\n\n"
Execute ./bs_${BOARD}_tests_bsim_bluetooth_audio_prj_conf \
-v=${VERBOSITY_LEVEL} -s=${SIMULATION_ID} -d=0 -testid=cap_commander_capture_and_render \
-rs=46 -D=3
Execute ./bs_${BOARD}_tests_bsim_bluetooth_audio_prj_conf \
-v=${VERBOSITY_LEVEL} -s=${SIMULATION_ID} -d=1 -testid=cap_acceptor_capture_and_render \
-rs=23 -D=3
Execute ./bs_${BOARD}_tests_bsim_bluetooth_audio_prj_conf \
-v=${VERBOSITY_LEVEL} -s=${SIMULATION_ID} -d=2 -testid=cap_acceptor_capture_and_render \
-rs=69 -D=3
# Simulation time should be larger than the WAIT_TIME in common.h
Execute ./bs_2G4_phy_v1 -v=${VERBOSITY_LEVEL} -s=${SIMULATION_ID} \
-D=3 -sim_length=60e6 $@
wait_for_background_jobs