Bluetooth: Audio: Make BSIM tests for PACS notify functionality

Added a BSIM tests for PACS notification functionality. This test
tests that all optional notifies are working, as well as verifying
that a disconnected subscribed client will be notified on reconnect.

Signed-off-by: Fredrik Danebjer <frdn@demant.com>
This commit is contained in:
Fredrik Danebjer 2023-08-14 15:21:51 +02:00 committed by Carles Cufí
commit 32b00dd6f4
8 changed files with 1024 additions and 190 deletions

View file

@ -34,8 +34,8 @@ config BT_PACS_SNK_CONTEXT
config BT_PAC_SNK_NOTIFIABLE config BT_PAC_SNK_NOTIFIABLE
bool "Sink PAC Notifiable Support" bool "Sink PAC Notifiable Support"
help help
This option enables support for clients to be notified of the Sink This option enables support for clients to be notified on the Sink
PAC Characteristic. PAC Characteristic changes.
config BT_PAC_SNK_LOC config BT_PAC_SNK_LOC
bool "Sink PAC Location Support" bool "Sink PAC Location Support"
@ -55,7 +55,7 @@ config BT_PAC_SNK_LOC_NOTIFIABLE
depends on BT_PAC_SNK_LOC depends on BT_PAC_SNK_LOC
help help
This option enables support for clients to be notified on the Sink This option enables support for clients to be notified on the Sink
Audio Location Characteristic. Audio Location Characteristic changes.
endif # BT_PACS_SNK endif # BT_PACS_SNK
@ -87,7 +87,7 @@ config BT_PAC_SRC_NOTIFIABLE
bool "Source PAC Notifiable Support" bool "Source PAC Notifiable Support"
help help
This option enables support for clients to be notified on the Source This option enables support for clients to be notified on the Source
PAC Characteristic. PAC Characteristic changes.
config BT_PAC_SRC_LOC config BT_PAC_SRC_LOC
bool "Source PAC Location Support" bool "Source PAC Location Support"
@ -107,7 +107,7 @@ config BT_PAC_SRC_LOC_NOTIFIABLE
depends on BT_PAC_SRC_LOC depends on BT_PAC_SRC_LOC
help help
This option enables support for clients to be notified on the Source This option enables support for clients to be notified on the Source
Audio Location Characteristic. Audio Location Characteristic changes.
endif # BT_PAC_SRC endif # BT_PAC_SRC
@ -119,6 +119,6 @@ config BT_PACS_SUPPORTED_CONTEXT_NOTIFIABLE
depends on BT_PACS depends on BT_PACS
help help
This option enables support for clients to be notified on the This option enables support for clients to be notified on the
Supported Audio Contexts Characteristic. Supported Audio Contexts Characteristic changes.
endmenu endmenu

View file

@ -66,22 +66,12 @@ static uint16_t src_supported_contexts = BT_AUDIO_CONTEXT_TYPE_PROHIBITED;
enum { enum {
FLAG_ACTIVE, FLAG_ACTIVE,
#if defined(CONFIG_BT_PAC_SNK_NOTIFIABLE)
FLAG_SINK_PAC_CHANGED, FLAG_SINK_PAC_CHANGED,
#endif /* CONFIG_BT_PAC_SNK_NOTIFIABLE) */
#if defined(CONFIG_BT_PAC_SNK_LOC_NOTIFIABLE)
FLAG_SINK_AUDIO_LOCATIONS_CHANGED, FLAG_SINK_AUDIO_LOCATIONS_CHANGED,
#endif /* CONFIG_BT_PAC_SNK_LOC_NOTIFIABLE */
#if defined(CONFIG_BT_PAC_SRC_NOTIFIABLE)
FLAG_SOURCE_PAC_CHANGED, FLAG_SOURCE_PAC_CHANGED,
#endif /* CONFIG_BT_PAC_SRC_NOTIFIABLE */
#if defined(CONFIG_BT_PAC_SRC_LOC_NOTIFIABLE)
FLAG_SOURCE_AUDIO_LOCATIONS_CHANGED, FLAG_SOURCE_AUDIO_LOCATIONS_CHANGED,
#endif
FLAG_AVAILABLE_AUDIO_CONTEXT_CHANGED, FLAG_AVAILABLE_AUDIO_CONTEXT_CHANGED,
#if defined(CONFIG_BT_PACS_SUPPORTED_CONTEXT_NOTIFIABLE)
FLAG_SUPPORTED_AUDIO_CONTEXT_CHANGED, FLAG_SUPPORTED_AUDIO_CONTEXT_CHANGED,
#endif /* CONFIG_BT_PACS_SUPPORTED_CONTEXT_NOTIFIABLE */
FLAG_NUM, FLAG_NUM,
}; };
@ -109,31 +99,6 @@ static void deferred_nfy_work_handler(struct k_work *work);
static K_WORK_DEFINE(deferred_nfy_work, deferred_nfy_work_handler); static K_WORK_DEFINE(deferred_nfy_work, deferred_nfy_work_handler);
static ssize_t pac_data_add(struct net_buf_simple *buf, size_t count,
struct bt_audio_codec_data *data)
{
size_t len = 0;
for (size_t i = 0U; i < count; i++) {
struct bt_pac_ltv *ltv;
struct bt_data *d = &data[i].data;
const size_t ltv_len = sizeof(*ltv) + d->data_len;
if (net_buf_simple_tailroom(buf) < ltv_len) {
return -ENOMEM;
}
ltv = net_buf_simple_add(buf, sizeof(*ltv));
ltv->len = d->data_len + sizeof(ltv->type);
ltv->type = d->type;
net_buf_simple_add_mem(buf, d->data, d->data_len);
len += ltv_len;
}
return len;
}
struct pac_records_build_data { struct pac_records_build_data {
struct bt_pacs_read_rsp *rsp; struct bt_pacs_read_rsp *rsp;
struct net_buf_simple *buf; struct net_buf_simple *buf;
@ -308,10 +273,10 @@ static int set_supported_contexts(uint16_t contexts, uint16_t *supported,
} }
} }
#if defined(CONFIG_BT_PACS_SUPPORTED_CONTEXT_NOTIFIABLE) if (IS_ENABLED(CONFIG_BT_PACS_SUPPORTED_CONTEXT_NOTIFIABLE)) {
pacs_set_notify_bit(FLAG_SUPPORTED_AUDIO_CONTEXT_CHANGED); pacs_set_notify_bit(FLAG_SUPPORTED_AUDIO_CONTEXT_CHANGED);
k_work_submit(&deferred_nfy_work); k_work_submit(&deferred_nfy_work);
#endif /* CONFIG_BT_PACS_SUPPORTED_CONTEXT_NOTIFIABLE */ }
return 0; return 0;
} }
@ -400,10 +365,10 @@ static void set_snk_location(enum bt_audio_location audio_location)
pacs_snk_location = audio_location; pacs_snk_location = audio_location;
#if defined(CONFIG_BT_PAC_SNK_LOC_NOTIFIABLE) if (IS_ENABLED(CONFIG_BT_PAC_SNK_LOC_NOTIFIABLE)) {
pacs_set_notify_bit(FLAG_SINK_AUDIO_LOCATIONS_CHANGED); pacs_set_notify_bit(FLAG_SINK_AUDIO_LOCATIONS_CHANGED);
k_work_submit(&deferred_nfy_work); k_work_submit(&deferred_nfy_work);
#endif /* CONFIG_BT_PAC_SNK_LOC_NOTIFIABLE */ }
} }
#else #else
static void set_snk_location(enum bt_audio_location location) static void set_snk_location(enum bt_audio_location location)
@ -465,10 +430,12 @@ static ssize_t src_read(struct bt_conn *conn, const struct bt_gatt_attr *attr,
return ret_val; return ret_val;
} }
#if defined(CONFIG_BT_PAC_SRC_LOC_NOTIFIABLE)
static void src_cfg_changed(const struct bt_gatt_attr *attr, uint16_t value) static void src_cfg_changed(const struct bt_gatt_attr *attr, uint16_t value)
{ {
LOG_DBG("attr %p value 0x%04x", attr, value); LOG_DBG("attr %p value 0x%04x", attr, value);
} }
#endif /* CONFIG_BT_PAC_SRC_LOC_NOTIFIABLE */
static inline int set_src_available_contexts(uint16_t contexts) static inline int set_src_available_contexts(uint16_t contexts)
{ {
@ -521,10 +488,10 @@ static void set_src_location(enum bt_audio_location audio_location)
pacs_src_location = audio_location; pacs_src_location = audio_location;
#if defined(CONFIG_BT_PAC_SRC_LOC_NOTIFIABLE) if (IS_ENABLED(CONFIG_BT_PAC_SRC_LOC_NOTIFIABLE)) {
pacs_set_notify_bit(FLAG_SOURCE_AUDIO_LOCATIONS_CHANGED); pacs_set_notify_bit(FLAG_SOURCE_AUDIO_LOCATIONS_CHANGED);
k_work_submit(&deferred_nfy_work); k_work_submit(&deferred_nfy_work);
#endif /* CONFIG_BT_PAC_SRC_LOC_NOTIFIABLE */ }
} }
#else #else
static void set_src_location(enum bt_audio_location location) static void set_src_location(enum bt_audio_location location)
@ -577,128 +544,122 @@ static sys_slist_t *pacs_get(enum bt_audio_dir dir)
} }
} }
#define BT_PACS_SNK_PROP \
BT_GATT_CHRC_READ \
IF_ENABLED(CONFIG_BT_PAC_SNK_LOC_NOTIFIABLE, (|BT_GATT_CHRC_NOTIFY))
#define BT_PAC_SNK(_read) \
BT_AUDIO_CHRC(BT_UUID_PACS_SNK, \
BT_PACS_SNK_PROP, \
BT_GATT_PERM_READ_ENCRYPT, \
_read, NULL, NULL), \
IF_ENABLED(CONFIG_BT_PAC_SNK_LOC_NOTIFIABLE, (BT_AUDIO_CCC(snk_cfg_changed),))
#define BT_PACS_SNK_LOC_PROP \
BT_GATT_CHRC_READ \
IF_ENABLED(CONFIG_BT_PAC_SNK_LOC_WRITEABLE, (|BT_GATT_CHRC_WRITE)) \
IF_ENABLED(CONFIG_BT_PAC_SNK_LOC_NOTIFIABLE, (|BT_GATT_CHRC_NOTIFY))
#define BT_PACS_SNK_LOC_PERM \
BT_GATT_PERM_READ_ENCRYPT \
IF_ENABLED(CONFIG_BT_PAC_SNK_LOC_WRITEABLE, (|BT_GATT_PERM_WRITE_ENCRYPT))
#define BT_PACS_SNK_LOC(_read) \
BT_AUDIO_CHRC(BT_UUID_PACS_SNK_LOC, \
BT_PACS_SNK_LOC_PROP, \
BT_PACS_SNK_LOC_PERM, \
_read, \
COND_CODE_1(CONFIG_BT_PAC_SNK_LOC_WRITEABLE, (snk_loc_write), (NULL)), \
NULL), \
IF_ENABLED(CONFIG_BT_PAC_SNK_LOC_NOTIFIABLE, (BT_AUDIO_CCC(snk_loc_cfg_changed),))
#define BT_PACS_SRC_PROP \
BT_GATT_CHRC_READ \
IF_ENABLED(CONFIG_BT_PAC_SRC_LOC_NOTIFIABLE, (|BT_GATT_CHRC_NOTIFY))
#define BT_PAC_SRC(_read) \
BT_AUDIO_CHRC(BT_UUID_PACS_SRC, \
BT_PACS_SRC_PROP, \
BT_GATT_PERM_READ_ENCRYPT, \
_read, NULL, NULL), \
IF_ENABLED(CONFIG_BT_PAC_SRC_LOC_NOTIFIABLE, (BT_AUDIO_CCC(src_cfg_changed),))
#define BT_PACS_SRC_LOC_PROP \
BT_GATT_CHRC_READ \
IF_ENABLED(CONFIG_BT_PAC_SRC_LOC_WRITEABLE, (|BT_GATT_CHRC_WRITE)) \
IF_ENABLED(CONFIG_BT_PAC_SRC_LOC_NOTIFIABLE, (|BT_GATT_CHRC_NOTIFY))
#define BT_PACS_SRC_LOC_PERM \
BT_GATT_PERM_READ_ENCRYPT \
IF_ENABLED(CONFIG_BT_PAC_SRC_LOC_WRITEABLE, (|BT_GATT_PERM_WRITE_ENCRYPT))
#define BT_PACS_SRC_LOC(_read) \
BT_AUDIO_CHRC(BT_UUID_PACS_SRC_LOC, \
BT_PACS_SRC_LOC_PROP, \
BT_PACS_SRC_LOC_PERM, \
_read, \
COND_CODE_1(CONFIG_BT_PAC_SRC_LOC_WRITEABLE, (src_loc_write), (NULL)), \
NULL), \
IF_ENABLED(CONFIG_BT_PAC_SRC_LOC_NOTIFIABLE, (BT_AUDIO_CCC(src_loc_cfg_changed),))
#define BT_PAC_AVAILABLE_CONTEXT(_read) \
BT_AUDIO_CHRC(BT_UUID_PACS_AVAILABLE_CONTEXT, \
BT_GATT_CHRC_READ|BT_GATT_CHRC_NOTIFY, \
BT_GATT_PERM_READ_ENCRYPT, \
_read, NULL, NULL), \
BT_AUDIO_CCC(available_context_cfg_changed),
#define BT_PACS_SUPPORTED_CONTEXT_PROP \
BT_GATT_CHRC_READ \
IF_ENABLED(CONFIG_BT_PACS_SUPPORTED_CONTEXT_NOTIFIABLE, (|BT_GATT_CHRC_NOTIFY))
#define BT_PAC_SUPPORTED_CONTEXT(_read) \
BT_AUDIO_CHRC(BT_UUID_PACS_SUPPORTED_CONTEXT, \
BT_PACS_SUPPORTED_CONTEXT_PROP, \
BT_GATT_PERM_READ_ENCRYPT, \
_read, NULL, NULL), \
IF_ENABLED(CONFIG_BT_PACS_SUPPORTED_CONTEXT_NOTIFIABLE, \
(BT_AUDIO_CCC(supported_context_cfg_changed),))
BT_GATT_SERVICE_DEFINE(pacs_svc, BT_GATT_SERVICE_DEFINE(pacs_svc,
BT_GATT_PRIMARY_SERVICE(BT_UUID_PACS), BT_GATT_PRIMARY_SERVICE(BT_UUID_PACS),
#if defined(CONFIG_BT_PAC_SNK) #if defined(CONFIG_BT_PAC_SNK)
#if defined(CONFIG_BT_PAC_SNK_NOTIFIABLE) BT_PAC_SNK(snk_read)
BT_AUDIO_CHRC(BT_UUID_PACS_SNK,
BT_GATT_CHRC_READ | BT_GATT_CHRC_NOTIFY,
BT_GATT_PERM_READ_ENCRYPT,
snk_read, NULL, NULL),
BT_AUDIO_CCC(snk_cfg_changed),
#else
BT_AUDIO_CHRC(BT_UUID_PACS_SNK,
BT_GATT_CHRC_READ,
BT_GATT_PERM_READ_ENCRYPT,
snk_read, NULL, NULL),
#endif /* CONFIG_BT_PAC_SNK_NOTIFIABLE */
#if defined(CONFIG_BT_PAC_SNK_LOC) #if defined(CONFIG_BT_PAC_SNK_LOC)
#if defined(CONFIG_BT_PAC_SNK_LOC_WRITEABLE) && defined(CONFIG_BT_PAC_SNK_LOC_NOTIFIABLE) BT_PACS_SNK_LOC(snk_loc_read)
BT_AUDIO_CHRC(BT_UUID_PACS_SNK_LOC,
BT_GATT_CHRC_READ | BT_GATT_CHRC_WRITE | BT_GATT_CHRC_NOTIFY,
BT_GATT_PERM_READ_ENCRYPT | BT_GATT_PERM_WRITE_ENCRYPT,
snk_loc_read, snk_loc_write, NULL),
BT_AUDIO_CCC(snk_loc_cfg_changed),
#elif defined(CONFIG_BT_PAC_SNK_LOC_NOTIFIABLE)
BT_AUDIO_CHRC(BT_UUID_PACS_SNK_LOC,
BT_GATT_CHRC_READ | BT_GATT_CHRC_NOTIFY,
BT_GATT_PERM_READ_ENCRYPT,
snk_loc_read, NULL, NULL),
BT_AUDIO_CCC(snk_loc_cfg_changed),
#elif defined(CONFIG_BT_PAC_SNK_LOC_WRITEABLE)
BT_AUDIO_CHRC(BT_UUID_PACS_SNK_LOC,
BT_GATT_CHRC_READ | BT_GATT_CHRC_WRITE,
BT_GATT_PERM_READ_ENCRYPT,
snk_loc_read, snk_loc_write, NULL),
#else
BT_AUDIO_CHRC(BT_UUID_PACS_SNK_LOC,
BT_GATT_CHRC_READ,
BT_GATT_PERM_READ_ENCRYPT,
snk_loc_read, NULL, NULL),
#endif /* (CONFIG_BT_PAC_SNK_LOC_WRITEABLE && CONFIG_BT_PAC_SNK_LOC_NOTIFIABLE) */
#endif /* CONFIG_BT_PAC_SNK_LOC */ #endif /* CONFIG_BT_PAC_SNK_LOC */
#endif /* CONFIG_BT_PAC_SNK */ #endif /* CONFIG_BT_PAC_SNK */
#if defined(CONFIG_BT_PAC_SRC) #if defined(CONFIG_BT_PAC_SRC)
#if defined(CONFIG_BT_PAC_SRC_NOTIFIABLE) BT_PAC_SRC(src_read)
BT_AUDIO_CHRC(BT_UUID_PACS_SRC,
BT_GATT_CHRC_READ | BT_GATT_CHRC_NOTIFY,
BT_GATT_PERM_READ_ENCRYPT,
src_read, NULL, NULL),
BT_AUDIO_CCC(src_cfg_changed),
#else
BT_AUDIO_CHRC(BT_UUID_PACS_SRC,
BT_GATT_CHRC_READ,
BT_GATT_PERM_READ_ENCRYPT,
src_read, NULL, NULL),
BT_AUDIO_CCC(src_cfg_changed),
#endif /* CONFIG_BT_PAC_SRC_NOTIFIABLE */
#if defined(CONFIG_BT_PAC_SRC_LOC) #if defined(CONFIG_BT_PAC_SRC_LOC)
#if defined(CONFIG_BT_PAC_SRC_LOC_WRITEABLE) && defined(CONFIG_BT_PAC_SRC_LOC_NOTIFIABLE) BT_PACS_SRC_LOC(src_loc_read)
BT_AUDIO_CHRC(BT_UUID_PACS_SRC_LOC,
BT_GATT_CHRC_READ | BT_GATT_CHRC_WRITE | BT_GATT_CHRC_NOTIFY,
BT_GATT_PERM_READ_ENCRYPT | BT_GATT_PERM_WRITE_ENCRYPT,
src_loc_read, src_loc_write, NULL),
BT_AUDIO_CCC(src_loc_cfg_changed),
#elif defined(CONFIG_BT_PAC_SRC_LOC_NOTIFIABLE)
BT_AUDIO_CHRC(BT_UUID_PACS_SRC_LOC,
BT_GATT_CHRC_READ | BT_GATT_CHRC_NOTIFY,
BT_GATT_PERM_READ_ENCRYPT,
src_loc_read, NULL, NULL),
BT_AUDIO_CCC(src_loc_cfg_changed),
#elif defined(CONFIG_BT_PAC_SRC_LOC_WRITEABLE)
BT_AUDIO_CHRC(BT_UUID_PACS_SRC_LOC,
BT_GATT_CHRC_READ | BT_GATT_CHRC_WRITE,
BT_GATT_PERM_READ_ENCRYPT,
src_loc_read, src_loc_write, NULL),
#else
BT_AUDIO_CHRC(BT_UUID_PACS_SRC_LOC,
BT_GATT_CHRC_READ,
BT_GATT_PERM_READ_ENCRYPT,
src_loc_read, NULL, NULL),
#endif /* (CONFIG_BT_PAC_SRC_LOC_WRITEABLE && CONFIG_BT_PAC_SRC_LOC_NOTIFIABLE) */
#endif /* CONFIG_BT_PAC_SRC_LOC */ #endif /* CONFIG_BT_PAC_SRC_LOC */
#endif /* CONFIG_BT_PAC_SRC */ #endif /* CONFIG_BT_PAC_SRC */
BT_AUDIO_CHRC(BT_UUID_PACS_AVAILABLE_CONTEXT, BT_PAC_AVAILABLE_CONTEXT(available_contexts_read)
BT_GATT_CHRC_READ | BT_GATT_CHRC_NOTIFY, BT_PAC_SUPPORTED_CONTEXT(supported_context_read)
BT_GATT_PERM_READ_ENCRYPT,
available_contexts_read, NULL, NULL),
BT_AUDIO_CCC(available_context_cfg_changed),
#if defined(CONFIG_BT_PACS_SUPPORTED_CONTEXT_NOTIFIABLE)
BT_AUDIO_CHRC(BT_UUID_PACS_SUPPORTED_CONTEXT,
BT_GATT_CHRC_READ | BT_GATT_CHRC_NOTIFY,
BT_GATT_PERM_READ_ENCRYPT,
supported_context_read, NULL, NULL),
BT_AUDIO_CCC(supported_context_cfg_changed)
#else
BT_AUDIO_CHRC(BT_UUID_PACS_SUPPORTED_CONTEXT,
BT_GATT_CHRC_READ,
BT_GATT_PERM_READ_ENCRYPT,
supported_context_read, NULL, NULL),
#endif /* CONFIG_BT_PACS_SUPPORTED_CONTEXT_NOTIFIABLE */
); );
#if defined(CONFIG_BT_PAC_SNK_LOC_NOTIFIABLE) || defined(CONFIG_BT_PAC_SRC_LOC_NOTIFIABLE)
static int pac_notify_loc(struct bt_conn *conn, enum bt_audio_dir dir) static int pac_notify_loc(struct bt_conn *conn, enum bt_audio_dir dir)
{ {
uint32_t location_le; uint32_t location_le;
int err; int err;
struct bt_uuid *uuid;
switch (dir) { switch (dir) {
#if defined(CONFIG_BT_PAC_SNK_LOC_NOTIFIABLE)
case BT_AUDIO_DIR_SINK: case BT_AUDIO_DIR_SINK:
#if defined(CONFIG_BT_PAC_SNK_LOC_NOTIFIABLE)
location_le = sys_cpu_to_le32(pacs_snk_location); location_le = sys_cpu_to_le32(pacs_snk_location);
uuid = BT_UUID_PACS_SNK_LOC;
break; break;
#endif /* CONFIG_BT_PAC_SNK */ #else
#if defined(CONFIG_BT_PAC_SRC_LOC_NOTIFIABLE) return -ENOTSUP;
#endif /* CONFIG_BT_PAC_SNK_LOC_NOTIFIABLE */
case BT_AUDIO_DIR_SOURCE: case BT_AUDIO_DIR_SOURCE:
#if defined(CONFIG_BT_PAC_SRC_LOC_NOTIFIABLE)
location_le = sys_cpu_to_le32(pacs_src_location); location_le = sys_cpu_to_le32(pacs_src_location);
uuid = BT_UUID_PACS_SRC_LOC;
break; break;
#endif /* CONFIG_BT_PAC_SRC */ #else
return -ENOTSUP;
#endif /* CONFIG_BT_PAC_SRC_LOC_NOTIFIABLE */
default: default:
return -EINVAL; return -EINVAL;
} }
err = pacs_gatt_notify(conn, BT_UUID_PACS_SRC_LOC, pacs_svc.attrs, &location_le, err = pacs_gatt_notify(conn, uuid, pacs_svc.attrs, &location_le,
sizeof(location_le)); sizeof(location_le));
if (err != 0 && err != -ENOTCONN) { if (err != 0 && err != -ENOTCONN) {
LOG_WRN("PACS notify_loc failed: %d", err); LOG_WRN("PACS notify_loc failed: %d", err);
@ -707,13 +668,25 @@ static int pac_notify_loc(struct bt_conn *conn, enum bt_audio_dir dir)
return 0; return 0;
} }
#endif /* CONFIG_BT_PAC_SNK_LOC_NOTIFIABLE || CONFIG_BT_PAC_SRC_LOC_NOTIFIABLE*/
#if defined(CONFIG_BT_PAC_SRC_NOTIFIABLE) || defined(CONFIG_BT_PAC_SNK_NOTIFIABLE)
static int pac_notify(struct bt_conn *conn, enum bt_audio_dir dir) static int pac_notify(struct bt_conn *conn, enum bt_audio_dir dir)
{ {
int err = 0; int err = 0;
sys_slist_t *pacs; sys_slist_t *pacs;
struct bt_uuid *uuid;
switch (dir) {
case BT_AUDIO_DIR_SINK:
__ASSERT(IS_ENABLED(CONFIG_BT_PAC_SNK_NOTIFIABLE), "Sink PAC not notifiable.\n");
uuid = BT_UUID_PACS_SNK;
break;
case BT_AUDIO_DIR_SOURCE:
__ASSERT(IS_ENABLED(CONFIG_BT_PAC_SRC_NOTIFIABLE), "Source PAC not notifiable.\n");
uuid = BT_UUID_PACS_SRC;
break;
default:
return -EINVAL;
}
err = k_sem_take(&read_buf_sem, K_NO_WAIT); err = k_sem_take(&read_buf_sem, K_NO_WAIT);
if (err != 0) { if (err != 0) {
@ -723,13 +696,10 @@ static int pac_notify(struct bt_conn *conn, enum bt_audio_dir dir)
} }
pacs = pacs_get(dir); pacs = pacs_get(dir);
if (!pacs) { __ASSERT(pacs, "Failed to get pacs.\n");
LOG_DBG("Failed to get pacs");
return -EINVAL;
}
get_pac_records(pacs, &read_buf); get_pac_records(pacs, &read_buf);
err = pacs_gatt_notify(conn, BT_UUID_PACS_SNK, pacs_svc.attrs, err = pacs_gatt_notify(conn, uuid, pacs_svc.attrs,
read_buf.data, read_buf.len); read_buf.data, read_buf.len);
if (err != 0 && err != -ENOTCONN) { if (err != 0 && err != -ENOTCONN) {
LOG_WRN("PACS notify failed: %d", err); LOG_WRN("PACS notify failed: %d", err);
@ -743,7 +713,6 @@ static int pac_notify(struct bt_conn *conn, enum bt_audio_dir dir)
return 0; return 0;
} }
} }
#endif /* CONFIG_BT_PAC_SRC_NOTIFIABLE|| CONFIG_BT_PAC_SNK_NOTIFIABLE */
static int available_contexts_notify(struct bt_conn *conn) static int available_contexts_notify(struct bt_conn *conn)
{ {
@ -763,7 +732,6 @@ static int available_contexts_notify(struct bt_conn *conn)
return 0; return 0;
} }
#if defined(CONFIG_BT_PACS_SUPPORTED_CONTEXT_NOTIFIABLE)
static int supported_contexts_notify(struct bt_conn *conn) static int supported_contexts_notify(struct bt_conn *conn)
{ {
struct bt_pacs_context context = { struct bt_pacs_context context = {
@ -781,7 +749,6 @@ static int supported_contexts_notify(struct bt_conn *conn)
} }
return 0; return 0;
} }
#endif /* CONFIG_BT_PACS_SUPPORTED_CONTEXT_NOTIFIABLE */
void pacs_gatt_notify_complete_cb(struct bt_conn *conn, void *user_data) void pacs_gatt_notify_complete_cb(struct bt_conn *conn, void *user_data)
{ {
@ -827,7 +794,7 @@ static void notify_cb(struct bt_conn *conn, void *data)
struct bt_conn_info info; struct bt_conn_info info;
int err = 0; int err = 0;
LOG_DBG("Notify cb"); LOG_DBG("");
err = bt_conn_get_info(conn, &info); err = bt_conn_get_info(conn, &info);
if (err != 0) { if (err != 0) {
@ -845,40 +812,56 @@ static void notify_cb(struct bt_conn *conn, void *data)
return; return;
} }
#if defined(CONFIG_BT_PAC_SNK_NOTIFIABLE) if (IS_ENABLED(CONFIG_BT_PAC_SNK_NOTIFIABLE) &&
if (atomic_test_and_clear_bit(client->flags, FLAG_SINK_PAC_CHANGED)) { atomic_test_bit(client->flags, FLAG_SINK_PAC_CHANGED)) {
LOG_DBG("Notifying Sink PAC"); LOG_DBG("Notifying Sink PAC");
pac_notify(conn, BT_AUDIO_DIR_SINK); err = pac_notify(conn, BT_AUDIO_DIR_SINK);
if (!err) {
atomic_clear_bit(client->flags, FLAG_SINK_PAC_CHANGED);
}
} }
#endif /* CONFIG_BT_PAC_SNK_NOTIFIABLE) */
#if defined(CONFIG_BT_PAC_SNK_LOC_NOTIFIABLE) if (IS_ENABLED(CONFIG_BT_PAC_SNK_LOC_NOTIFIABLE) &&
if (atomic_test_and_clear_bit(client->flags, FLAG_SINK_AUDIO_LOCATIONS_CHANGED)) { atomic_test_bit(client->flags, FLAG_SINK_AUDIO_LOCATIONS_CHANGED)) {
LOG_DBG("Notifying Sink Audio Location"); LOG_DBG("Notifying Sink Audio Location");
pac_notify_loc(conn, BT_AUDIO_DIR_SINK); err = pac_notify_loc(conn, BT_AUDIO_DIR_SINK);
if (!err) {
atomic_clear_bit(client->flags, FLAG_SINK_AUDIO_LOCATIONS_CHANGED);
}
} }
#endif /* CONFIG_BT_PAC_SNK_LOC_NOTIFIABLE */ if (IS_ENABLED(CONFIG_BT_PAC_SRC_NOTIFIABLE) &&
#if defined(CONFIG_BT_PAC_SRC_NOTIFIABLE) atomic_test_bit(client->flags, FLAG_SOURCE_PAC_CHANGED)) {
if (atomic_test_and_clear_bit(client->flags, FLAG_SOURCE_PAC_CHANGED)) {
LOG_DBG("Notifying Source PAC"); LOG_DBG("Notifying Source PAC");
pac_notify(conn, BT_AUDIO_DIR_SOURCE); err = pac_notify(conn, BT_AUDIO_DIR_SOURCE);
if (!err) {
atomic_clear_bit(client->flags, FLAG_SOURCE_PAC_CHANGED);
}
} }
#endif /* CONFIG_BT_PAC_SRC_NOTIFIABLE */ if (IS_ENABLED(CONFIG_BT_PAC_SRC_LOC_NOTIFIABLE) &&
#if defined(CONFIG_BT_PAC_SRC_LOC_NOTIFIABLE) atomic_test_and_clear_bit(client->flags, FLAG_SOURCE_AUDIO_LOCATIONS_CHANGED)) {
if (atomic_test_and_clear_bit(client->flags, FLAG_SOURCE_AUDIO_LOCATIONS_CHANGED)) {
LOG_DBG("Notifying Source Audio Location"); LOG_DBG("Notifying Source Audio Location");
pac_notify_loc(conn, BT_AUDIO_DIR_SOURCE); err = pac_notify_loc(conn, BT_AUDIO_DIR_SOURCE);
if (!err) {
atomic_clear_bit(client->flags, FLAG_SOURCE_AUDIO_LOCATIONS_CHANGED);
}
} }
#endif
if (atomic_test_and_clear_bit(client->flags, FLAG_AVAILABLE_AUDIO_CONTEXT_CHANGED)) { if (atomic_test_bit(client->flags, FLAG_AVAILABLE_AUDIO_CONTEXT_CHANGED)) {
LOG_DBG("Notifying Available Contexts"); LOG_DBG("Notifying Available Contexts");
available_contexts_notify(conn); err = available_contexts_notify(conn);
if (!err) {
atomic_clear_bit(client->flags, FLAG_AVAILABLE_AUDIO_CONTEXT_CHANGED);
}
} }
#if defined(CONFIG_BT_PACS_SUPPORTED_CONTEXT_NOTIFIABLE)
if (atomic_test_and_clear_bit(client->flags, FLAG_SUPPORTED_AUDIO_CONTEXT_CHANGED)) { if (IS_ENABLED(CONFIG_BT_PACS_SUPPORTED_CONTEXT_NOTIFIABLE) &&
atomic_test_bit(client->flags, FLAG_SUPPORTED_AUDIO_CONTEXT_CHANGED)) {
LOG_DBG("Notifying Supported Contexts"); LOG_DBG("Notifying Supported Contexts");
supported_contexts_notify(conn); err = supported_contexts_notify(conn);
if (!err) {
atomic_clear_bit(client->flags, FLAG_SUPPORTED_AUDIO_CONTEXT_CHANGED);
}
} }
#endif /* CONFIG_BT_PACS_SUPPORTED_CONTEXT_NOTIFIABLE */
} }
static void deferred_nfy_work_handler(struct k_work *work) static void deferred_nfy_work_handler(struct k_work *work)
@ -946,7 +929,6 @@ static void pacs_security_changed(struct bt_conn *conn, bt_security_t level,
} }
for (size_t i = 0U; i < ARRAY_SIZE(clients); i++) { for (size_t i = 0U; i < ARRAY_SIZE(clients); i++) {
for (size_t j = 0U; j < FLAG_NUM; j++) { for (size_t j = 0U; j < FLAG_NUM; j++) {
if (atomic_test_bit(clients[i].flags, j)) { if (atomic_test_bit(clients[i].flags, j)) {
@ -1049,23 +1031,16 @@ int bt_pacs_cap_register(enum bt_audio_dir dir, struct bt_pacs_cap *cap)
callbacks_registered = true; callbacks_registered = true;
} }
if (IS_ENABLED(CONFIG_BT_PAC_SNK_NOTIFIABLE) && dir == BT_AUDIO_DIR_SINK) {
#if defined(CONFIG_BT_PAC_SNK_NOTIFIABLE)
if (dir == BT_AUDIO_DIR_SINK) {
pacs_set_notify_bit(FLAG_SINK_PAC_CHANGED); pacs_set_notify_bit(FLAG_SINK_PAC_CHANGED);
k_work_submit(&deferred_nfy_work); k_work_submit(&deferred_nfy_work);
} }
#endif /* CONFIG_BT_PAC_SNK_NOTIFIABLE */ if (IS_ENABLED(CONFIG_BT_PAC_SRC_NOTIFIABLE) && dir == BT_AUDIO_DIR_SOURCE) {
#if defined(CONFIG_BT_PAC_SRC_NOTIFIABLE)
if (dir == BT_AUDIO_DIR_SOURCE) {
pacs_set_notify_bit(FLAG_SOURCE_PAC_CHANGED); pacs_set_notify_bit(FLAG_SOURCE_PAC_CHANGED);
k_work_submit(&deferred_nfy_work); k_work_submit(&deferred_nfy_work);
} }
#endif /* CONFIG_BT_PAC_SRC_NOTIFIABLE */
return 0; return 0;
} }

View file

@ -127,6 +127,21 @@ CONFIG_BT_CAP_INITIATOR=y
# Telephony and Media Audio Profile # Telephony and Media Audio Profile
CONFIG_BT_TMAP=y CONFIG_BT_TMAP=y
# Publish Audio Capability configurations
CONFIG_BT_PAC_SNK=y
CONFIG_BT_PAC_SRC=y
CONFIG_BT_PAC_SNK_LOC=y
CONFIG_BT_PAC_SRC_LOC=y
CONFIG_BT_PACS_SUPPORTED_CONTEXT_NOTIFIABLE=y
CONFIG_BT_PAC_SNK_LOC_NOTIFIABLE=y
CONFIG_BT_PAC_SRC_LOC_NOTIFIABLE=y
CONFIG_BT_PAC_SRC_NOTIFIABLE=y
CONFIG_BT_PAC_SNK_NOTIFIABLE=y
CONFIG_BT_PAC_SNK_LOC_WRITEABLE=y
CONFIG_BT_PAC_SRC_LOC_WRITEABLE=y
CONFIG_BT_GATT_AUTO_DISCOVER_CCC=y
# DEBUGGING # DEBUGGING
CONFIG_LOG=y CONFIG_LOG=y
CONFIG_LOG_FUNC_NAME_PREFIX_ERR=y CONFIG_LOG_FUNC_NAME_PREFIX_ERR=y

View file

@ -41,6 +41,11 @@
while (!(bool)atomic_get(&flag)) { \ while (!(bool)atomic_get(&flag)) { \
(void)k_sleep(K_MSEC(1)); \ (void)k_sleep(K_MSEC(1)); \
} }
#define WAIT_FOR_UNSET_FLAG(flag) \
while (atomic_get(&flag) != (atomic_t)false) { \
(void)k_sleep(K_MSEC(1)); \
}
#define FAIL(...) \ #define FAIL(...) \
do { \ do { \

View file

@ -33,6 +33,8 @@ extern struct bst_test_list *test_ias_install(struct bst_test_list *tests);
extern struct bst_test_list *test_ias_client_install(struct bst_test_list *tests); extern struct bst_test_list *test_ias_client_install(struct bst_test_list *tests);
extern struct bst_test_list *test_tmap_client_install(struct bst_test_list *tests); extern struct bst_test_list *test_tmap_client_install(struct bst_test_list *tests);
extern struct bst_test_list *test_tmap_server_install(struct bst_test_list *tests); extern struct bst_test_list *test_tmap_server_install(struct bst_test_list *tests);
extern struct bst_test_list *test_pacs_notify_client_install(struct bst_test_list *tests);
extern struct bst_test_list *test_pacs_notify_server_install(struct bst_test_list *tests);
bst_test_install_t test_installers[] = { bst_test_install_t test_installers[] = {
test_vcp_install, test_vcp_install,
@ -62,6 +64,8 @@ bst_test_install_t test_installers[] = {
test_ias_client_install, test_ias_client_install,
test_tmap_server_install, test_tmap_server_install,
test_tmap_client_install, test_tmap_client_install,
test_pacs_notify_client_install,
test_pacs_notify_server_install,
NULL NULL
}; };

View file

@ -0,0 +1,576 @@
/*
* Copyright (c) 2023 Demant A/S
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/bluetooth/bluetooth.h>
#include <zephyr/bluetooth/gatt.h>
#include "common.h"
#include "common/bt_str.h"
struct pacs_instance_t {
uint16_t start_handle;
uint16_t end_handle;
struct bt_gatt_subscribe_params sink_pacs_sub;
struct bt_gatt_subscribe_params source_pacs_sub;
struct bt_gatt_subscribe_params sink_loc_sub;
struct bt_gatt_subscribe_params source_loc_sub;
struct bt_gatt_subscribe_params available_contexts_sub;
struct bt_gatt_subscribe_params supported_contexts_sub;
struct bt_gatt_discover_params discover_params;
int notify_received_mask;
};
extern enum bst_result_t bst_result;
CREATE_FLAG(flag_pacs_snk_discovered);
CREATE_FLAG(flag_pacs_src_discovered);
CREATE_FLAG(flag_snk_loc_discovered);
CREATE_FLAG(flag_src_loc_discovered);
CREATE_FLAG(flag_available_contexts_discovered);
CREATE_FLAG(flag_supported_contexts_discovered);
CREATE_FLAG(flag_all_notifications_received);
static struct bt_uuid_16 uuid = BT_UUID_INIT_16(0);
static struct pacs_instance_t pacs_instance;
static uint8_t pacs_notify_handler(struct bt_conn *conn,
struct bt_gatt_subscribe_params *params,
const void *data, uint16_t length)
{
printk("%p\n", params);
if (params == &pacs_instance.sink_pacs_sub) {
printk("Received sink_pacs_sub notification\n");
pacs_instance.notify_received_mask |= BIT(0);
} else if (params == &pacs_instance.source_pacs_sub) {
printk("Received source_pacs_sub notification\n");
pacs_instance.notify_received_mask |= BIT(1);
} else if (params == &pacs_instance.sink_loc_sub) {
printk("Received sink_loc_sub notification\n");
pacs_instance.notify_received_mask |= BIT(2);
} else if (params == &pacs_instance.source_loc_sub) {
printk("Received source_loc_sub notification\n");
pacs_instance.notify_received_mask |= BIT(3);
} else if (params == &pacs_instance.available_contexts_sub) {
printk("Received available_contexts_sub notification\n");
pacs_instance.notify_received_mask |= BIT(4);
} else if (params == &pacs_instance.supported_contexts_sub) {
printk("Received supported_contexts_sub notification\n");
pacs_instance.notify_received_mask |= BIT(5);
}
printk("pacs_instance.notify_received_mask is %d\n", pacs_instance.notify_received_mask);
if (pacs_instance.notify_received_mask ==
(BIT(0) | BIT(1) | BIT(2) | BIT(3) | BIT(4) | BIT(5))) {
pacs_instance.notify_received_mask = 0;
SET_FLAG(flag_all_notifications_received);
}
return BT_GATT_ITER_CONTINUE;
}
static uint8_t discover_supported_contexts(struct bt_conn *conn, const struct bt_gatt_attr *attr,
struct bt_gatt_discover_params *params)
{
struct bt_gatt_subscribe_params *subscribe_params;
int err;
if (!attr) {
printk("Discover complete\n");
(void)memset(params, 0, sizeof(*params));
return BT_GATT_ITER_STOP;
}
if (!bt_uuid_cmp(params->uuid, BT_UUID_PACS_SUPPORTED_CONTEXT)) {
printk("PACS Supported Contexts Characteristic handle at %d\n", attr->handle);
subscribe_params = &pacs_instance.supported_contexts_sub;
memcpy(&uuid, BT_UUID_GATT_CCC, sizeof(uuid));
pacs_instance.discover_params.uuid = &uuid.uuid;
pacs_instance.discover_params.start_handle = attr->handle + 2;
pacs_instance.discover_params.type = BT_GATT_DISCOVER_DESCRIPTOR;
subscribe_params->value_handle = bt_gatt_attr_value_handle(attr);
err = bt_gatt_discover(conn, &pacs_instance.discover_params);
if (err) {
printk("Discover failed (err %d)\n", err);
}
} else if (!bt_uuid_cmp(params->uuid, BT_UUID_GATT_CCC)) {
printk("CCC handle at %d\n", attr->handle);
subscribe_params = &pacs_instance.supported_contexts_sub;
subscribe_params->notify = pacs_notify_handler;
subscribe_params->value = BT_GATT_CCC_NOTIFY;
subscribe_params->ccc_handle = attr->handle;
err = bt_gatt_subscribe(conn, subscribe_params);
if (err && err != -EALREADY) {
printk("Subscribe failed (err %d)\n", err);
} else {
SET_FLAG(flag_supported_contexts_discovered);
printk("[SUBSCRIBED]\n");
}
} else {
printk("Unknown handle at %d\n", attr->handle);
return BT_GATT_ITER_CONTINUE;
}
return BT_GATT_ITER_STOP;
}
static void discover_and_subscribe_supported_contexts(void)
{
int err = 0;
printk("%s\n", __func__);
memcpy(&uuid, BT_UUID_PACS_SUPPORTED_CONTEXT, sizeof(uuid));
pacs_instance.discover_params.uuid = &uuid.uuid;
pacs_instance.discover_params.start_handle = BT_ATT_FIRST_ATTRIBUTE_HANDLE;
pacs_instance.discover_params.end_handle = BT_ATT_LAST_ATTRIBUTE_HANDLE;
pacs_instance.discover_params.type = BT_GATT_DISCOVER_CHARACTERISTIC;
pacs_instance.discover_params.func = discover_supported_contexts;
err = bt_gatt_discover(default_conn, &pacs_instance.discover_params);
if (err != 0) {
FAIL("Service Discovery failed (err %d)\n", err);
return;
}
}
static uint8_t discover_available_contexts(struct bt_conn *conn, const struct bt_gatt_attr *attr,
struct bt_gatt_discover_params *params)
{
struct bt_gatt_subscribe_params *subscribe_params;
int err;
if (!attr) {
printk("Discover complete\n");
(void)memset(params, 0, sizeof(*params));
return BT_GATT_ITER_STOP;
}
if (!bt_uuid_cmp(params->uuid, BT_UUID_PACS_AVAILABLE_CONTEXT)) {
printk("PACS Available Contexts Characteristic handle at %d\n", attr->handle);
subscribe_params = &pacs_instance.available_contexts_sub;
memcpy(&uuid, BT_UUID_GATT_CCC, sizeof(uuid));
pacs_instance.discover_params.uuid = &uuid.uuid;
pacs_instance.discover_params.start_handle = attr->handle + 2;
pacs_instance.discover_params.type = BT_GATT_DISCOVER_DESCRIPTOR;
subscribe_params->value_handle = bt_gatt_attr_value_handle(attr);
err = bt_gatt_discover(conn, &pacs_instance.discover_params);
if (err) {
printk("Discover failed (err %d)\n", err);
}
} else if (!bt_uuid_cmp(params->uuid, BT_UUID_GATT_CCC)) {
printk("CCC handle at %d\n", attr->handle);
subscribe_params = &pacs_instance.available_contexts_sub;
subscribe_params->notify = pacs_notify_handler;
subscribe_params->value = BT_GATT_CCC_NOTIFY;
subscribe_params->ccc_handle = attr->handle;
err = bt_gatt_subscribe(conn, subscribe_params);
if (err && err != -EALREADY) {
printk("Subscribe failed (err %d)\n", err);
} else {
SET_FLAG(flag_available_contexts_discovered);
printk("[SUBSCRIBED]\n");
}
} else {
printk("Unknown handle at %d\n", attr->handle);
return BT_GATT_ITER_CONTINUE;
}
return BT_GATT_ITER_STOP;
}
static void discover_and_subscribe_available_contexts(void)
{
int err = 0;
printk("%s\n", __func__);
memcpy(&uuid, BT_UUID_PACS_AVAILABLE_CONTEXT, sizeof(uuid));
pacs_instance.discover_params.uuid = &uuid.uuid;
pacs_instance.discover_params.start_handle = BT_ATT_FIRST_ATTRIBUTE_HANDLE;
pacs_instance.discover_params.end_handle = BT_ATT_LAST_ATTRIBUTE_HANDLE;
pacs_instance.discover_params.type = BT_GATT_DISCOVER_CHARACTERISTIC;
pacs_instance.discover_params.func = discover_available_contexts;
err = bt_gatt_discover(default_conn, &pacs_instance.discover_params);
if (err != 0) {
FAIL("Service Discovery failed (err %d)\n", err);
return;
}
}
static uint8_t discover_src_loc(struct bt_conn *conn, const struct bt_gatt_attr *attr,
struct bt_gatt_discover_params *params)
{
struct bt_gatt_subscribe_params *subscribe_params;
int err;
if (!attr) {
printk("Discover complete\n");
(void)memset(params, 0, sizeof(*params));
return BT_GATT_ITER_STOP;
}
if (!bt_uuid_cmp(params->uuid, BT_UUID_PACS_SRC_LOC)) {
printk("PACS Source Location Characteristic handle at %d\n", attr->handle);
subscribe_params = &pacs_instance.source_loc_sub;
memcpy(&uuid, BT_UUID_GATT_CCC, sizeof(uuid));
pacs_instance.discover_params.uuid = &uuid.uuid;
pacs_instance.discover_params.start_handle = attr->handle + 2;
pacs_instance.discover_params.type = BT_GATT_DISCOVER_DESCRIPTOR;
subscribe_params->value_handle = bt_gatt_attr_value_handle(attr);
err = bt_gatt_discover(conn, &pacs_instance.discover_params);
if (err) {
printk("Discover failed (err %d)\n", err);
}
} else if (!bt_uuid_cmp(params->uuid, BT_UUID_GATT_CCC)) {
printk("CCC handle at %d\n", attr->handle);
subscribe_params = &pacs_instance.source_loc_sub;
subscribe_params->notify = pacs_notify_handler;
subscribe_params->value = BT_GATT_CCC_NOTIFY;
subscribe_params->ccc_handle = attr->handle;
err = bt_gatt_subscribe(conn, subscribe_params);
if (err && err != -EALREADY) {
printk("Subscribe failed (err %d)\n", err);
} else {
SET_FLAG(flag_src_loc_discovered);
printk("[SUBSCRIBED]\n");
}
} else {
printk("Unknown handle at %d\n", attr->handle);
return BT_GATT_ITER_CONTINUE;
}
return BT_GATT_ITER_STOP;
}
static void discover_and_subscribe_src_loc(void)
{
int err = 0;
printk("%s\n", __func__);
memcpy(&uuid, BT_UUID_PACS_SRC_LOC, sizeof(uuid));
pacs_instance.discover_params.uuid = &uuid.uuid;
pacs_instance.discover_params.start_handle = BT_ATT_FIRST_ATTRIBUTE_HANDLE;
pacs_instance.discover_params.end_handle = BT_ATT_LAST_ATTRIBUTE_HANDLE;
pacs_instance.discover_params.type = BT_GATT_DISCOVER_CHARACTERISTIC;
pacs_instance.discover_params.func = discover_src_loc;
err = bt_gatt_discover(default_conn, &pacs_instance.discover_params);
if (err != 0) {
FAIL("Service Discovery failed (err %d)\n", err);
return;
}
}
static uint8_t discover_snk_loc(struct bt_conn *conn, const struct bt_gatt_attr *attr,
struct bt_gatt_discover_params *params)
{
struct bt_gatt_subscribe_params *subscribe_params;
int err;
if (!attr) {
printk("Discover complete\n");
(void)memset(params, 0, sizeof(*params));
return BT_GATT_ITER_STOP;
}
if (!bt_uuid_cmp(params->uuid, BT_UUID_PACS_SNK_LOC)) {
printk("PACS Sink Location Characteristic handle at %d\n", attr->handle);
subscribe_params = &pacs_instance.sink_loc_sub;
memcpy(&uuid, BT_UUID_GATT_CCC, sizeof(uuid));
pacs_instance.discover_params.uuid = &uuid.uuid;
pacs_instance.discover_params.start_handle = attr->handle + 2;
pacs_instance.discover_params.type = BT_GATT_DISCOVER_DESCRIPTOR;
subscribe_params->value_handle = bt_gatt_attr_value_handle(attr);
err = bt_gatt_discover(conn, &pacs_instance.discover_params);
if (err) {
printk("Discover failed (err %d)\n", err);
}
} else if (!bt_uuid_cmp(params->uuid, BT_UUID_GATT_CCC)) {
printk("CCC handle at %d\n", attr->handle);
subscribe_params = &pacs_instance.sink_loc_sub;
subscribe_params->notify = pacs_notify_handler;
subscribe_params->value = BT_GATT_CCC_NOTIFY;
subscribe_params->ccc_handle = attr->handle;
err = bt_gatt_subscribe(conn, subscribe_params);
if (err && err != -EALREADY) {
printk("Subscribe failed (err %d)\n", err);
} else {
SET_FLAG(flag_snk_loc_discovered);
printk("[SUBSCRIBED]\n");
}
} else {
printk("Unknown handle at %d\n", attr->handle);
return BT_GATT_ITER_CONTINUE;
}
return BT_GATT_ITER_STOP;
}
static void discover_and_subscribe_snk_loc(void)
{
int err = 0;
printk("%s\n", __func__);
memcpy(&uuid, BT_UUID_PACS_SNK_LOC, sizeof(uuid));
pacs_instance.discover_params.uuid = &uuid.uuid;
pacs_instance.discover_params.start_handle = BT_ATT_FIRST_ATTRIBUTE_HANDLE;
pacs_instance.discover_params.end_handle = BT_ATT_LAST_ATTRIBUTE_HANDLE;
pacs_instance.discover_params.type = BT_GATT_DISCOVER_CHARACTERISTIC;
pacs_instance.discover_params.func = discover_snk_loc;
err = bt_gatt_discover(default_conn, &pacs_instance.discover_params);
if (err != 0) {
FAIL("Service Discovery failed (err %d)\n", err);
return;
}
}
static uint8_t discover_pacs_src(struct bt_conn *conn, const struct bt_gatt_attr *attr,
struct bt_gatt_discover_params *params)
{
struct bt_gatt_subscribe_params *subscribe_params;
int err;
if (!attr) {
printk("Discover complete\n");
(void)memset(params, 0, sizeof(*params));
return BT_GATT_ITER_STOP;
}
if (!bt_uuid_cmp(params->uuid, BT_UUID_PACS_SRC)) {
printk("PACS Source Characteristic handle at %d\n", attr->handle);
subscribe_params = &pacs_instance.source_pacs_sub;
memcpy(&uuid, BT_UUID_GATT_CCC, sizeof(uuid));
pacs_instance.discover_params.uuid = &uuid.uuid;
pacs_instance.discover_params.start_handle = attr->handle + 2;
pacs_instance.discover_params.type = BT_GATT_DISCOVER_DESCRIPTOR;
subscribe_params->value_handle = bt_gatt_attr_value_handle(attr);
err = bt_gatt_discover(conn, &pacs_instance.discover_params);
if (err) {
printk("Discover failed (err %d)\n", err);
}
} else if (!bt_uuid_cmp(params->uuid, BT_UUID_GATT_CCC)) {
printk("CCC handle at %d\n", attr->handle);
subscribe_params = &pacs_instance.source_pacs_sub;
subscribe_params->notify = pacs_notify_handler;
subscribe_params->value = BT_GATT_CCC_NOTIFY;
subscribe_params->ccc_handle = attr->handle;
err = bt_gatt_subscribe(conn, subscribe_params);
if (err && err != -EALREADY) {
printk("Subscribe failed (err %d)\n", err);
} else {
SET_FLAG(flag_pacs_src_discovered);
printk("[SUBSCRIBED]\n");
}
} else {
printk("Unknown handle at %d\n", attr->handle);
return BT_GATT_ITER_CONTINUE;
}
return BT_GATT_ITER_STOP;
}
static void discover_and_subscribe_src_pacs(void)
{
int err = 0;
printk("%s\n", __func__);
memcpy(&uuid, BT_UUID_PACS_SRC, sizeof(uuid));
pacs_instance.discover_params.uuid = &uuid.uuid;
pacs_instance.discover_params.start_handle = BT_ATT_FIRST_ATTRIBUTE_HANDLE;
pacs_instance.discover_params.end_handle = BT_ATT_LAST_ATTRIBUTE_HANDLE;
pacs_instance.discover_params.type = BT_GATT_DISCOVER_CHARACTERISTIC;
pacs_instance.discover_params.func = discover_pacs_src;
err = bt_gatt_discover(default_conn, &pacs_instance.discover_params);
if (err != 0) {
FAIL("Service Discovery failed (err %d)\n", err);
return;
}
}
static uint8_t discover_pacs_snk(struct bt_conn *conn, const struct bt_gatt_attr *attr,
struct bt_gatt_discover_params *params)
{
struct bt_gatt_subscribe_params *subscribe_params;
int err;
if (!attr) {
printk("Discover complete\n");
(void)memset(params, 0, sizeof(*params));
return BT_GATT_ITER_STOP;
}
if (!bt_uuid_cmp(params->uuid, BT_UUID_PACS_SNK)) {
printk("PACS Sink Characteristic handle at %d\n", attr->handle);
subscribe_params = &pacs_instance.sink_pacs_sub;
memcpy(&uuid, BT_UUID_GATT_CCC, sizeof(uuid));
pacs_instance.discover_params.uuid = &uuid.uuid;
pacs_instance.discover_params.start_handle = attr->handle + 2;
pacs_instance.discover_params.type = BT_GATT_DISCOVER_DESCRIPTOR;
subscribe_params->value_handle = bt_gatt_attr_value_handle(attr);
err = bt_gatt_discover(conn, &pacs_instance.discover_params);
if (err) {
printk("Discover failed (err %d)\n", err);
}
} else if (!bt_uuid_cmp(params->uuid, BT_UUID_GATT_CCC)) {
printk("CCC handle at %d\n", attr->handle);
subscribe_params = &pacs_instance.sink_pacs_sub;
subscribe_params->notify = pacs_notify_handler;
subscribe_params->value = BT_GATT_CCC_NOTIFY;
subscribe_params->ccc_handle = attr->handle;
err = bt_gatt_subscribe(conn, subscribe_params);
if (err && err != -EALREADY) {
printk("Subscribe failed (err %d)\n", err);
} else {
SET_FLAG(flag_pacs_snk_discovered);
printk("[SUBSCRIBED]\n");
}
} else {
printk("Unknown handle at %d\n", attr->handle);
return BT_GATT_ITER_CONTINUE;
}
return BT_GATT_ITER_STOP;
}
static void discover_and_subscribe_snk_pacs(void)
{
int err = 0;
printk("%s\n", __func__);
memcpy(&uuid, BT_UUID_PACS_SNK, sizeof(uuid));
pacs_instance.discover_params.uuid = &uuid.uuid;
pacs_instance.discover_params.start_handle = BT_ATT_FIRST_ATTRIBUTE_HANDLE;
pacs_instance.discover_params.end_handle = BT_ATT_LAST_ATTRIBUTE_HANDLE;
pacs_instance.discover_params.type = BT_GATT_DISCOVER_CHARACTERISTIC;
pacs_instance.discover_params.func = discover_pacs_snk;
err = bt_gatt_discover(default_conn, &pacs_instance.discover_params);
if (err != 0) {
FAIL("Service Discovery failed (err %d)\n", err);
return;
}
}
static void test_main(void)
{
int err;
printk("Enabling Bluetooth\n");
err = bt_enable(NULL);
if (err != 0) {
FAIL("Bluetooth enable failed (err %d)\n", err);
return;
}
printk("Starting scan\n");
err = bt_le_scan_start(BT_LE_SCAN_PASSIVE, device_found);
if (err != 0) {
FAIL("Could not start scanning (err %d)\n", err);
return;
}
WAIT_FOR_FLAG(flag_connected);
printk("Raising security\n");
err = bt_conn_set_security(default_conn, BT_SECURITY_L2);
if (err) {
FAIL("Failed to ser security level %d (err %d)\n", BT_SECURITY_L2, err);
return;
}
printk("Starting Discovery\n");
discover_and_subscribe_snk_pacs();
WAIT_FOR_FLAG(flag_pacs_snk_discovered);
discover_and_subscribe_snk_loc();
WAIT_FOR_FLAG(flag_snk_loc_discovered);
discover_and_subscribe_src_pacs();
WAIT_FOR_FLAG(flag_pacs_src_discovered);
discover_and_subscribe_src_loc();
WAIT_FOR_FLAG(flag_src_loc_discovered);
discover_and_subscribe_available_contexts();
WAIT_FOR_FLAG(flag_available_contexts_discovered);
discover_and_subscribe_supported_contexts();
WAIT_FOR_FLAG(flag_supported_contexts_discovered);
printk("Waiting for all notifications to be received\n");
WAIT_FOR_FLAG(flag_all_notifications_received);
/* Disconnect and wait for server to advertise again (after notifications are triggered) */
UNSET_FLAG(flag_all_notifications_received);
bt_conn_disconnect(default_conn, BT_HCI_ERR_REMOTE_USER_TERM_CONN);
WAIT_FOR_UNSET_FLAG(flag_connected);
printk("Starting scan\n");
err = bt_le_scan_start(BT_LE_SCAN_PASSIVE, device_found);
if (err != 0) {
FAIL("Could not start scanning (err %d)\n", err);
return;
}
WAIT_FOR_FLAG(flag_connected);
printk("Raising security\n");
err = bt_conn_set_security(default_conn, BT_SECURITY_L2);
if (err) {
FAIL("Failed to ser security level %d (err %d)\n", BT_SECURITY_L2, err);
return;
}
printk("Waiting for all notifications to be received\n");
WAIT_FOR_FLAG(flag_all_notifications_received);
bt_conn_disconnect(default_conn, BT_HCI_ERR_REMOTE_USER_TERM_CONN);
WAIT_FOR_UNSET_FLAG(flag_connected);
PASS("GATT client Passed\n");
}
static const struct bst_test_instance test_pacs_notify_client[] = {
{
.test_id = "pacs_notify_client",
.test_post_init_f = test_init,
.test_tick_f = test_tick,
.test_main_f = test_main,
},
BSTEST_END_MARKER,
};
struct bst_test_list *test_pacs_notify_client_install(struct bst_test_list *tests)
{
return bst_add_tests(tests, test_pacs_notify_client);
}

View file

@ -0,0 +1,233 @@
/*
* Copyright (c) 2023 Demant A/S
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/bluetooth/audio/pacs.h>
#include "common.h"
extern enum bst_result_t bst_result;
static struct bt_audio_codec_cap lc3_codec_1 =
BT_AUDIO_CODEC_CAP_LC3(BT_AUDIO_CODEC_LC3_FREQ_16KHZ | BT_AUDIO_CODEC_LC3_FREQ_24KHZ,
BT_AUDIO_CODEC_LC3_DURATION_10,
BT_AUDIO_CODEC_LC3_CHAN_COUNT_SUPPORT(1), 40u, 60u, 1u,
BT_AUDIO_CONTEXT_TYPE_ANY);
static struct bt_audio_codec_cap lc3_codec_2 =
BT_AUDIO_CODEC_CAP_LC3(BT_AUDIO_CODEC_LC3_FREQ_16KHZ,
BT_AUDIO_CODEC_LC3_DURATION_10,
BT_AUDIO_CODEC_LC3_CHAN_COUNT_SUPPORT(1), 40u, 60u, 1u,
BT_AUDIO_CONTEXT_TYPE_ANY);
static struct bt_pacs_cap caps_1 = {
.codec_cap = &lc3_codec_1,
};
static struct bt_pacs_cap caps_2 = {
.codec_cap = &lc3_codec_2,
};
static bool is_peer_subscribed(struct bt_conn *conn)
{
struct bt_gatt_attr *attr;
uint8_t nbr_subscribed = 0;
attr = bt_gatt_find_by_uuid(NULL, 0, BT_UUID_PACS_SNK);
if (!attr) {
printk("No BT_UUID_PACS_SNK attribute found\n");
}
if (bt_gatt_is_subscribed(conn, attr, BT_GATT_CCC_NOTIFY)) {
nbr_subscribed++;
}
attr = bt_gatt_find_by_uuid(NULL, 0, BT_UUID_PACS_SNK_LOC);
if (!attr) {
printk("No BT_UUID_PACS_SNK_LOC attribute found\n");
}
if (bt_gatt_is_subscribed(conn, attr, BT_GATT_CCC_NOTIFY)) {
nbr_subscribed++;
}
attr = bt_gatt_find_by_uuid(NULL, 0, BT_UUID_PACS_SRC);
if (!attr) {
printk("No BT_UUID_PACS_SRC attribute found\n");
}
if (bt_gatt_is_subscribed(conn, attr, BT_GATT_CCC_NOTIFY)) {
nbr_subscribed++;
}
attr = bt_gatt_find_by_uuid(NULL, 0, BT_UUID_PACS_SRC_LOC);
if (!attr) {
printk("No BT_UUID_PACS_SRC_LOC attribute found\n");
}
if (bt_gatt_is_subscribed(conn, attr, BT_GATT_CCC_NOTIFY)) {
nbr_subscribed++;
}
attr = bt_gatt_find_by_uuid(NULL, 0, BT_UUID_PACS_AVAILABLE_CONTEXT);
if (!attr) {
printk("No BT_UUID_PACS_AVAILABLE_CONTEXT attribute found\n");
}
if (bt_gatt_is_subscribed(conn, attr, BT_GATT_CCC_NOTIFY)) {
nbr_subscribed++;
}
attr = bt_gatt_find_by_uuid(NULL, 0, BT_UUID_PACS_SUPPORTED_CONTEXT);
if (!attr) {
printk("No BT_UUID_PACS_SUPPORTED_CONTEXT attribute found\n");
}
if (bt_gatt_is_subscribed(conn, attr, BT_GATT_CCC_NOTIFY)) {
nbr_subscribed++;
}
if (nbr_subscribed != 6) {
return false;
}
return true;
}
static void trigger_notifications(void)
{
static enum bt_audio_context available = BT_AUDIO_CONTEXT_TYPE_ANY;
static enum bt_audio_context supported = BT_AUDIO_CONTEXT_TYPE_ANY;
static int i;
int err;
struct bt_pacs_cap *caps;
enum bt_audio_location snk_loc;
enum bt_audio_location src_loc;
printk("Triggering Notifications\n");
if (i) {
caps = &caps_1;
snk_loc = BT_AUDIO_LOCATION_FRONT_LEFT;
src_loc = BT_AUDIO_LOCATION_FRONT_RIGHT;
i = 0;
} else {
caps = &caps_2;
snk_loc = BT_AUDIO_LOCATION_FRONT_RIGHT;
src_loc = BT_AUDIO_LOCATION_FRONT_LEFT;
i++;
}
printk("Changing Sink PACs\n");
bt_pacs_cap_register(BT_AUDIO_DIR_SINK, caps);
bt_pacs_cap_register(BT_AUDIO_DIR_SOURCE, caps);
printk("Changing Sink Location\n");
err = bt_pacs_set_location(BT_AUDIO_DIR_SINK, snk_loc);
if (err != 0) {
printk("Failed to set device sink location\n");
}
printk("Changing Source Location\n");
err = bt_pacs_set_location(BT_AUDIO_DIR_SOURCE, src_loc);
if (err != 0) {
printk("Failed to set device source location\n");
}
printk("Changing Supported Contexts Location\n");
supported = supported ^ BT_AUDIO_CONTEXT_TYPE_MEDIA;
bt_pacs_set_supported_contexts(BT_AUDIO_DIR_SINK, supported);
printk("Changing Available Contexts\n");
available = available ^ BT_AUDIO_CONTEXT_TYPE_MEDIA;
bt_pacs_set_available_contexts(BT_AUDIO_DIR_SINK, available);
}
static void test_main(void)
{
int err;
const struct bt_data ad[] = {
BT_DATA_BYTES(BT_DATA_FLAGS, (BT_LE_AD_GENERAL | BT_LE_AD_NO_BREDR)),
};
printk("Enabling Bluetooth\n");
err = bt_enable(NULL);
if (err != 0) {
FAIL("Bluetooth enable failed (err %d)\n", err);
return;
}
bt_pacs_set_supported_contexts(BT_AUDIO_DIR_SINK, BT_AUDIO_CONTEXT_TYPE_ANY);
bt_pacs_set_supported_contexts(BT_AUDIO_DIR_SOURCE, BT_AUDIO_CONTEXT_TYPE_ANY);
bt_pacs_set_available_contexts(BT_AUDIO_DIR_SINK, BT_AUDIO_CONTEXT_TYPE_ANY);
bt_pacs_set_available_contexts(BT_AUDIO_DIR_SOURCE, BT_AUDIO_CONTEXT_TYPE_ANY);
printk("Registereding PACS\n");
bt_pacs_cap_register(BT_AUDIO_DIR_SINK, &caps_1);
bt_pacs_cap_register(BT_AUDIO_DIR_SOURCE, &caps_1);
err = bt_pacs_set_location(BT_AUDIO_DIR_SINK, BT_AUDIO_LOCATION_FRONT_LEFT);
if (err != 0) {
printk("Failed to set device sink location\n");
return;
}
err = bt_pacs_set_location(BT_AUDIO_DIR_SOURCE, BT_AUDIO_LOCATION_FRONT_RIGHT);
if (err != 0) {
printk("Failed to set device source location\n");
return;
}
printk("Start Advertising\n");
err = bt_le_adv_start(BT_LE_ADV_CONN_NAME, ad, ARRAY_SIZE(ad), NULL, 0);
if (err != 0) {
FAIL("Advertising failed to start (err %d)\n", err);
return;
}
printk("Waiting to be connected\n");
WAIT_FOR_FLAG(flag_connected);
printk("Connected\n");
printk("Waiting to be subscribed\n");
while (!is_peer_subscribed(default_conn)) {
(void)k_sleep(K_MSEC(10));
}
printk("Subscribed\n");
trigger_notifications();
/* Now wait for client to disconnect, then stop adv so it does not reconnect */
printk("Wait for client disconnect\n");
WAIT_FOR_UNSET_FLAG(flag_connected);
printk("Client disconnected\n");
err = bt_le_adv_stop();
if (err != 0) {
FAIL("Advertising failed to stop (err %d)\n", err);
return;
}
/* Trigger changes while device is disconnected */
trigger_notifications();
printk("Start Advertising\n");
err = bt_le_adv_start(BT_LE_ADV_CONN_NAME, ad, ARRAY_SIZE(ad), NULL, 0);
if (err != 0) {
FAIL("Advertising failed to start (err %d)\n", err);
return;
}
WAIT_FOR_FLAG(flag_connected);
WAIT_FOR_UNSET_FLAG(flag_connected);
PASS("PACS Notify Server passed\n");
}
static const struct bst_test_instance test_pacs_notify_server[] = {
{
.test_id = "pacs_notify_server",
.test_post_init_f = test_init,
.test_tick_f = test_tick,
.test_main_f = test_main,
},
BSTEST_END_MARKER,
};
struct bst_test_list *test_pacs_notify_server_install(struct bst_test_list *tests)
{
return bst_add_tests(tests, test_pacs_notify_server);
}

View file

@ -0,0 +1,26 @@
#!/usr/bin/env bash
#
# Copyright (c) 2023 Demant A/S
#
# SPDX-License-Identifier: Apache-2.0
source ${ZEPHYR_BASE}/tests/bsim/sh_common.source
SIMULATION_ID="pacs_notify"
VERBOSITY_LEVEL=2
EXECUTE_TIMEOUT=200
cd ${BSIM_OUT_PATH}/bin
printf "\n\n======== Running PACS Notify test =========\n\n"
Execute ./bs_${BOARD}_tests_bsim_bluetooth_audio_prj_conf \
-v=${VERBOSITY_LEVEL} -s=${SIMULATION_ID} -d=0 -testid=pacs_notify_server -rs=24
Execute ./bs_${BOARD}_tests_bsim_bluetooth_audio_prj_conf \
-v=${VERBOSITY_LEVEL} -s=${SIMULATION_ID} -d=1 -testid=pacs_notify_client -rs=46
Execute ./bs_2G4_phy_v1 -v=${VERBOSITY_LEVEL} -s=${SIMULATION_ID} \
-D=2 -sim_length=60e6 $@
wait_for_background_jobs