Bluetooth: Audio: Add PACS location read for unicast client

The unicast client will now read the PACS location value
during the discovery procedure, as well as subscribing to
it.

Signed-off-by: Emil Gydesen <emil.gydesen@nordicsemi.no>
This commit is contained in:
Emil Gydesen 2022-06-27 15:47:02 +02:00 committed by Fabio Baltieri
parent 5aa1ab9c1a
commit df75bfd8f6
5 changed files with 313 additions and 4 deletions

View file

@ -1301,6 +1301,34 @@ struct bt_audio_stream {
sys_snode_t _node;
};
/** Unicast Client callback structure */
struct bt_audio_unicast_client_cb {
/** @brief Remote Unicast Server Audio Locations
*
* This callback is called whenever the audio locations is read from
* the server or otherwise notified to the client.
*
* @param conn Connection to the remote unicast server.
* @param dir Direction of the location.
* @param loc The location bitfield value.
*
* @return 0 in case of success or negative value in case of error.
*/
void (*location)(struct bt_conn *conn, enum bt_audio_dir dir,
enum bt_audio_location loc);
};
/** @brief Register unicast client callbacks.
*
* Only one callback structure can be registered, and attempting to
* registering more than one will result in an error.
*
* @param cb Unicast client callback structure.
*
* @return 0 in case of success or negative value in case of error.
*/
int bt_audio_unicast_client_register_cb(const struct bt_audio_unicast_client_cb *cb);
/** Unicast Server callback structure */
struct bt_audio_unicast_server_cb {
/** @brief Endpoint config request callback

View file

@ -717,6 +717,17 @@ static struct bt_gatt_cb gatt_callbacks = {
.att_mtu_updated = att_mtu_updated,
};
static void unicast_client_location_cb(struct bt_conn *conn,
enum bt_audio_dir dir,
enum bt_audio_location loc)
{
printk("dir %u loc %X\n", dir, loc);
}
const struct bt_audio_unicast_client_cb unicast_client_cbs = {
.location = unicast_client_location_cb
};
static int init(void)
{
int err;
@ -1016,6 +1027,12 @@ void main(void)
}
printk("Initialized\n");
err = bt_audio_unicast_client_register_cb(&unicast_client_cbs);
if (err != 0) {
printk("Failed to register client callbacks: %d", err);
return;
}
while (true) {
reset_data();

View file

@ -11,6 +11,7 @@
#include <zephyr/zephyr.h>
#include <zephyr/sys/byteorder.h>
#include <zephyr/sys/check.h>
#include <zephyr/bluetooth/bluetooth.h>
#include <zephyr/bluetooth/conn.h>
@ -39,6 +40,8 @@ static struct unicast_client_pac {
static const struct bt_uuid *snk_uuid = BT_UUID_PACS_SNK;
static const struct bt_uuid *src_uuid = BT_UUID_PACS_SRC;
static const struct bt_uuid *pacs_context_uuid = BT_UUID_PACS_SUPPORTED_CONTEXT;
static const struct bt_uuid *pacs_snk_loc_uuid = BT_UUID_PACS_SNK_LOC;
static const struct bt_uuid *pacs_src_loc_uuid = BT_UUID_PACS_SRC_LOC;
static const struct bt_uuid *ase_snk_uuid = BT_UUID_ASCS_ASE_SNK;
static const struct bt_uuid *ase_src_uuid = BT_UUID_ASCS_ASE_SRC;
static const struct bt_uuid *cp_uuid = BT_UUID_ASCS_ASE_CP;
@ -49,8 +52,18 @@ static struct bt_audio_iso audio_isos[CONFIG_BT_AUDIO_UNICAST_CLIENT_GROUP_COUNT
[CONFIG_BT_AUDIO_UNICAST_CLIENT_GROUP_STREAM_COUNT];
static struct bt_gatt_subscribe_params cp_subscribe[CONFIG_BT_MAX_CONN];
static struct bt_gatt_subscribe_params snk_loc_subscribe[CONFIG_BT_MAX_CONN];
static struct bt_gatt_subscribe_params src_loc_subscribe[CONFIG_BT_MAX_CONN];
/* TODO: We should be able to reduce the number of discover params for CCCD
* discovery, but requires additional work if it is to support an arbitrary
* number of EATT bearers, as control point and location CCCD discovery
* needs to be done in serial to avoid using the same discover parameters
* twice
*/
static struct bt_gatt_discover_params cp_disc[CONFIG_BT_MAX_CONN];
static struct bt_gatt_discover_params loc_cc_disc[CONFIG_BT_MAX_CONN];
static const struct bt_audio_unicast_client_cb *unicast_client_cbs;
/* TODO: Move the functions to avoid these prototypes */
static int unicast_client_ep_set_metadata(struct bt_audio_ep *ep,
@ -1895,6 +1908,199 @@ static int unicast_client_ase_discover(struct bt_conn *conn,
return bt_gatt_read(conn, &params->read);
}
static uint8_t unicast_client_pacs_location_read_func(struct bt_conn *conn,
uint8_t err,
struct bt_gatt_read_params *read,
const void *data,
uint16_t length)
{
struct bt_audio_discover_params *params;
struct net_buf_simple buf;
uint32_t location;
params = CONTAINER_OF(read, struct bt_audio_discover_params, read);
BT_DBG("conn %p err 0x%02x len %u", conn, err, length);
if (err || data == NULL || length != sizeof(location)) {
BT_DBG("Unable to read PACS location for dir %u: %u, %p, %u",
params->dir, err, data, length);
params->func(conn, NULL, NULL, params);
return BT_GATT_ITER_STOP;
}
net_buf_simple_init_with_data(&buf, (void *)data, length);
location = net_buf_simple_pull_le32(&buf);
BT_DBG("dir %u loc %X", params->dir, location);
if (unicast_client_cbs != NULL && unicast_client_cbs->location != NULL) {
unicast_client_cbs->location(conn, params->dir,
(enum bt_audio_location)location);
}
/* Read ASE instances */
if (unicast_client_ase_discover(conn, params) < 0) {
BT_ERR("Unable to read ASE");
params->func(conn, NULL, NULL, params);
}
return BT_GATT_ITER_STOP;
}
static uint8_t unicast_client_pacs_location_notify_cb(struct bt_conn *conn,
struct bt_gatt_subscribe_params *params,
const void *data,
uint16_t length)
{
struct net_buf_simple buf;
enum bt_audio_dir dir;
uint32_t location;
BT_DBG("conn %p len %u", conn, length);
if (!data) {
BT_DBG("Unsubscribed");
params->value_handle = 0x0000;
return BT_GATT_ITER_STOP;
}
/* Terminate early if there's no callbacks */
if (unicast_client_cbs == NULL || unicast_client_cbs->location == NULL) {
return BT_GATT_ITER_CONTINUE;
}
net_buf_simple_init_with_data(&buf, (void *)data, length);
if (buf.len != sizeof(location)) {
BT_ERR("Location notification incorrect size: %u", length);
return BT_GATT_ITER_STOP;
}
if (PART_OF_ARRAY(snk_loc_subscribe, params)) {
dir = BT_AUDIO_DIR_SINK;
} else if (PART_OF_ARRAY(src_loc_subscribe, params)) {
dir = BT_AUDIO_DIR_SOURCE;
} else {
BT_ERR("Invalid notification");
return BT_GATT_ITER_CONTINUE;
}
net_buf_simple_init_with_data(&buf, (void *)data, length);
location = net_buf_simple_pull_le32(&buf);
BT_DBG("dir %u loc %X", dir, location);
if (unicast_client_cbs != NULL && unicast_client_cbs->location != NULL) {
unicast_client_cbs->location(conn, dir,
(enum bt_audio_location)location);
}
return BT_GATT_ITER_CONTINUE;
}
static int unicast_client_pacs_location_read(struct bt_conn *conn,
struct bt_audio_discover_params *params,
uint16_t handle)
{
BT_DBG("conn %p params %p", conn, params);
params->read.func = unicast_client_pacs_location_read_func;
params->read.handle_count = 1U;
params->read.single.handle = handle;
params->read.single.offset = 0U;
return bt_gatt_read(conn, &params->read);
}
static uint8_t unicast_client_pacs_location_discover_cb(struct bt_conn *conn,
const struct bt_gatt_attr *attr,
struct bt_gatt_discover_params *discover)
{
struct bt_audio_discover_params *params;
uint8_t index = bt_conn_index(conn);
const struct bt_gatt_chrc *chrc;
int err;
params = CONTAINER_OF(discover, struct bt_audio_discover_params,
discover);
if (!attr) {
/* If location is not found, we just continue reading the ASEs,
* as location is optional.
*/
if (unicast_client_ase_discover(conn, params) < 0) {
BT_ERR("Unable to read ASE");
params->func(conn, NULL, NULL, params);
}
return BT_GATT_ITER_STOP;
}
chrc = attr->user_data;
BT_DBG("conn %p attr %p handle 0x%04x", conn, attr, chrc->value_handle);
if (chrc->properties & BT_GATT_CHRC_NOTIFY) {
struct bt_gatt_subscribe_params *sub_params;
if (params->dir == BT_AUDIO_DIR_SINK) {
sub_params = &snk_loc_subscribe[index];
} else {
sub_params = &src_loc_subscribe[index];
}
sub_params->value_handle = chrc->value_handle;
sub_params->ccc_handle = 0x0000; /* auto discover ccc */
sub_params->end_handle = BT_ATT_LAST_ATTRIBUTE_HANDLE;
sub_params->disc_params = &loc_cc_disc[index];
sub_params->notify = unicast_client_pacs_location_notify_cb;
sub_params->value = BT_GATT_CCC_NOTIFY;
err = bt_gatt_subscribe(conn, sub_params);
if (err != 0) {
BT_ERR("Failed to subscribe to location: %d", err);
}
}
err = unicast_client_pacs_location_read(conn, params, chrc->value_handle);
if (err != 0) {
BT_DBG("Failed to read PACS location: %d", err);
params->err = err;
params->func(conn, NULL, NULL, params);
}
return BT_GATT_ITER_STOP;
}
static int unicast_client_pacs_location_discover(struct bt_conn *conn,
struct bt_audio_discover_params *params)
{
BT_DBG("conn %p params %p", conn, params);
if (params->dir == BT_AUDIO_DIR_SINK) {
params->discover.uuid = pacs_snk_loc_uuid;
} else if (params->dir == BT_AUDIO_DIR_SOURCE) {
params->discover.uuid = pacs_src_loc_uuid;
} else {
return -EINVAL;
}
params->discover.func = unicast_client_pacs_location_discover_cb;
params->discover.start_handle = BT_ATT_FIRST_ATTRIBUTE_HANDLE;
params->discover.end_handle = BT_ATT_LAST_ATTRIBUTE_HANDLE;
params->discover.type = BT_GATT_DISCOVER_CHARACTERISTIC;
return bt_gatt_discover(conn, &params->discover);
}
static uint8_t unicast_client_pacs_context_read_func(struct bt_conn *conn,
uint8_t err,
struct bt_gatt_read_params *read,
@ -1911,7 +2117,7 @@ static uint8_t unicast_client_pacs_context_read_func(struct bt_conn *conn,
BT_DBG("conn %p err 0x%02x len %u", conn, err, length);
if (err || length < sizeof(uint16_t) * 2) {
goto discover_ase;
goto discover_loc;
}
net_buf_simple_init_with_data(&buf, (void *)data, length);
@ -1941,10 +2147,10 @@ static uint8_t unicast_client_pacs_context_read_func(struct bt_conn *conn,
BT_DBG("pac %p context 0x%04x", pac, pac->context);
}
discover_ase:
discover_loc:
/* Read ASE instances */
if (unicast_client_ase_discover(conn, params) < 0) {
BT_ERR("Unable to read ASE");
if (unicast_client_pacs_location_discover(conn, params) < 0) {
BT_ERR("Unable to read PACS location");
params->func(conn, NULL, NULL, params);
}
@ -2170,3 +2376,21 @@ int bt_audio_discover(struct bt_conn *conn,
return bt_gatt_read(conn, &params->read);
}
int bt_audio_unicast_client_register_cb(const struct bt_audio_unicast_client_cb *cbs)
{
CHECKIF(cbs == NULL) {
BT_DBG("cbs is NULL");
return -EINVAL;
}
if (unicast_client_cbs != NULL) {
BT_DBG("Callbacks already registered");
return -EALREADY;
}
unicast_client_cbs = cbs;
return 0;
}

View file

@ -590,9 +590,21 @@ static void discover_all(struct bt_conn *conn, struct bt_codec *codec,
}
}
static void unicast_client_location_cb(struct bt_conn *conn,
enum bt_audio_dir dir,
enum bt_audio_location loc)
{
shell_print(ctx_shell, "dir %u loc %X\n", dir, loc);
}
const struct bt_audio_unicast_client_cb unicast_client_cbs = {
.location = unicast_client_location_cb
};
static int cmd_discover(const struct shell *sh, size_t argc, char *argv[])
{
static struct bt_audio_discover_params params;
static bool cbs_registered;
if (!default_conn) {
shell_error(sh, "Not connected");
@ -604,6 +616,17 @@ static int cmd_discover(const struct shell *sh, size_t argc, char *argv[])
return -ENOEXEC;
}
if (!cbs_registered) {
int err = bt_audio_unicast_client_register_cb(&unicast_client_cbs);
if (err != 0) {
shell_error(sh, "Failed to register unicast client callbacks: %d", err);
return err;
}
cbs_registered = true;
}
params.func = discover_all;
params.dir = BT_AUDIO_DIR_SINK;

View file

@ -95,6 +95,17 @@ static struct bt_audio_stream_ops stream_ops = {
.released = stream_released,
};
static void unicast_client_location_cb(struct bt_conn *conn,
enum bt_audio_dir dir,
enum bt_audio_location loc)
{
printk("dir %u loc %X\n", dir, loc);
}
const struct bt_audio_unicast_client_cb unicast_client_cbs = {
.location = unicast_client_location_cb
};
static void add_remote_sink(struct bt_audio_ep *ep, uint8_t index)
{
printk("Sink #%u: ep %p\n", index, ep);
@ -207,6 +218,12 @@ static void init(void)
}
bt_gatt_cb_register(&gatt_callbacks);
err = bt_audio_unicast_client_register_cb(&unicast_client_cbs);
if (err != 0) {
FAIL("Failed to register client callbacks: %d", err);
return;
}
}
static void scan_and_connect(void)