Skip to content

Commit

Permalink
Bluetooth: CAP: Commander discovery support
Browse files Browse the repository at this point in the history
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 <[email protected]>
  • Loading branch information
Thalley committed Dec 7, 2023
1 parent b1b2bbb commit c45be7c
Show file tree
Hide file tree
Showing 25 changed files with 918 additions and 13 deletions.
37 changes: 37 additions & 0 deletions doc/connectivity/bluetooth/api/shell/cap.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
56 changes: 51 additions & 5 deletions include/zephyr/bluetooth/audio/cap.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down Expand Up @@ -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.
*
Expand Down Expand Up @@ -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.
*
Expand All @@ -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. */
Expand Down
54 changes: 52 additions & 2 deletions subsys/bluetooth/audio/cap_commander.c
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
17 changes: 13 additions & 4 deletions subsys/bluetooth/audio/cap_common.c
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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;
}
1 change: 0 additions & 1 deletion subsys/bluetooth/audio/ccid_internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
#ifndef ZEPHYR_INCLUDE_BLUETOOTH_CCID_H_
#define ZEPHYR_INCLUDE_BLUETOOTH_CCID_H_

#include <zephyr/device.h>
#include <zephyr/types.h>

/**
Expand Down
4 changes: 4 additions & 0 deletions subsys/bluetooth/audio/shell/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
79 changes: 79 additions & 0 deletions subsys/bluetooth/audio/shell/cap_commander.c
Original file line number Diff line number Diff line change
@@ -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);
18 changes: 18 additions & 0 deletions tests/bluetooth/audio/cap_commander/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -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
)
18 changes: 18 additions & 0 deletions tests/bluetooth/audio/cap_commander/prj.conf
Original file line number Diff line number Diff line change
@@ -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
Loading

0 comments on commit c45be7c

Please sign in to comment.