Bluetooth: Audio: Add TMAS and two TMAP samples

Add implementation of the Telephony and Media Audio Service, as well as
two sample applications.

tmap_central reflects a smartphone implementing the Unicast Media Sender
and Call Gateway TMAP roles.

tmap_peripheral reflects an earbud implementing the Unicast Media
Receiver and Call Terminal TMAP roles.

Upon connection, tmap_central starts an audio stream using CAP Initiator
APIs.

CCP, MCP and VCP are discovered and used to send example commands.

Future improvements: 2-earbud support, add TMAP Broadcast roles,
update with new CAP Acceptor/Commander APIs as they become available

Signed-off-by: Silviu Petria <silviu.petria@nxp.com>
This commit is contained in:
Silviu Petria 2023-05-04 15:44:56 +03:00 committed by Carles Cufí
commit 5096aa1c00
41 changed files with 3287 additions and 0 deletions

View file

@ -0,0 +1,66 @@
/** @file
* @brief Header for Bluetooth TMAP.
*
* Copyright 2023 NXP
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef ZEPHYR_INCLUDE_BLUETOOTH_AUDIO_TMAP_
#define ZEPHYR_INCLUDE_BLUETOOTH_AUDIO_TMAP_
#include <zephyr/sys/util.h>
/** @brief TMAP Role characteristic */
enum bt_tmap_role {
BT_TMAP_ROLE_CG = BIT(0),
BT_TMAP_ROLE_CT = BIT(1),
BT_TMAP_ROLE_UMS = BIT(2),
BT_TMAP_ROLE_UMR = BIT(3),
BT_TMAP_ROLE_BMS = BIT(4),
BT_TMAP_ROLE_BMR = BIT(5),
};
/** @brief TMAP callback structure. */
struct bt_tmap_cb {
/** @brief TMAP discovery complete callback
*
* This callback notifies the application about the value of the
* TMAP Role characteristic on the peer.
*
* @param role Peer TMAP role(s).
* @param conn Pointer to the connection
* @param err 0 if success, ATT error received from server otherwise.
*/
void (*discovery_complete)(enum bt_tmap_role role, struct bt_conn *conn, int err);
};
/**
* @brief Adds TMAS instance to database and sets the received TMAP role(s).
*
* @param role TMAP role(s) of the device (one or multiple).
*
* @return 0 on success or negative error value on failure.
*/
int bt_tmap_register(enum bt_tmap_role role);
/**
* @brief Perform service discovery as TMAP Client
*
* @param conn Pointer to the connection.
* @param tmap_cb Pointer to struct of TMAP callbacks.
*
* @return 0 on success or negative error value on failure.
*/
int bt_tmap_discover(struct bt_conn *conn, struct bt_tmap_cb *tmap_cb);
/**
* @brief Set one or multiple TMAP roles dynamically.
* Previously registered value will be overwritten.
*
* @param role TMAP role(s).
*
*/
void bt_tmap_set_role(enum bt_tmap_role role);
#endif /* ZEPHYR_INCLUDE_BLUETOOTH_AUDIO_TMAP_ */

View file

@ -0,0 +1,15 @@
# SPDX-License-Identifier: Apache-2.0
cmake_minimum_required(VERSION 3.20.0)
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
project(tmap_central)
target_sources(app PRIVATE
src/main.c
src/mcp_server.c
src/vcp_vol_ctlr.c
src/ccp_server.c
src/cap_initiator.c
)
zephyr_library_include_directories(${ZEPHYR_BASE}/samples/bluetooth)

View file

@ -0,0 +1,22 @@
.. _bluetooth_tmap_central:
Bluetooth: TMAP Central
#######################
Overview
********
Application demonstrating the LE Audio TMAP central functionality. Implements the CG and UMS roles.
Requirements
************
* A board with Bluetooth Low Energy 5.2 support
Building and Running
********************
This sample can be found under
:zephyr_file:`samples/bluetooth/tmap_central` in the Zephyr tree.
See :ref:`bluetooth samples section <bluetooth-samples>` for details.

View file

@ -0,0 +1,10 @@
CONFIG_LOG_MODE_IMMEDIATE=y
CONFIG_BT_TINYCRYPT_ECC=y
CONFIG_LIBLC3=y
CONFIG_FPU=y
# For LE-audio at 10ms intervals we need the tick counter to occur more frequently
# than every 10 ms as each PDU for some reason takes 2 ticks to process.
CONFIG_SYS_CLOCK_TICKS_PER_SEC=1000
CONFIG_NATIVE_POSIX_SLOWDOWN_TO_REAL_TIME=y

View file

@ -0,0 +1,4 @@
CONFIG_BT_CTLR_CENTRAL_ISO=y
# Supports the highest SDU size required by any BAP LC3 presets (155)
CONFIG_BT_CTLR_ISO_TX_BUFFER_SIZE=155

View file

@ -0,0 +1,8 @@
# For LC3 the following configs are needed
CONFIG_FPU=y
CONFIG_LIBLC3=y
# The LC3 codec uses a large amount of stack. This app runs the codec in the work-queue, hence
# inctease stack size for that thread.
CONFIG_SYSTEM_WORKQUEUE_STACK_SIZE=4096
# LC3 lib requires floating point support in the c-lib NEWLIB is one way of getting that.
CONFIG_NEWLIB_LIBC=y

View file

@ -0,0 +1,8 @@
# For LC3 the following configs are needed
CONFIG_FPU=y
CONFIG_LIBLC3=y
# The LC3 codec uses a large amount of stack. This app runs the codec in the work-queue, hence
# inctease stack size for that thread.
CONFIG_SYSTEM_WORKQUEUE_STACK_SIZE=4096
# LC3 lib requires floating point support in the c-lib NEWLIB is one way of getting that.
CONFIG_NEWLIB_LIBC=y

View file

@ -0,0 +1,47 @@
CONFIG_BT=y
CONFIG_BT_DEBUG_LOG=y
CONFIG_BT_CENTRAL=y
CONFIG_BT_AUDIO=y
CONFIG_BT_SMP=y
CONFIG_BT_KEYS_OVERWRITE_OLDEST=y
CONFIG_BT_L2CAP_TX_BUF_COUNT=20
# TMAP support
CONFIG_BT_TMAP=y
# CAP support
CONFIG_BT_CAP_INITIATOR=y
# CSIP support
CONFIG_BT_CSIP_SET_COORDINATOR=y
# BAP support
CONFIG_BT_BAP_UNICAST_CLIENT=y
# VCP support
CONFIG_BT_VCP_VOL_CTLR=y
# MCP support
CONFIG_BT_MPL=y
CONFIG_BT_MCS=y
CONFIG_MCTL_LOCAL_PLAYER_REMOTE_CONTROL=y
CONFIG_UTF8=y
CONFIG_MCTL_LOCAL_PLAYER_CONTROL=y
CONFIG_MCTL=y
# CCP support
CONFIG_BT_TBS=y
CONFIG_BT_GTBS=y
CONFIG_BT_TBS_SUPPORTED_FEATURES=3
# Support an ISO channel per ASE
CONFIG_BT_ISO_TX_BUF_COUNT=2
CONFIG_BT_ISO_MAX_CHAN=2
CONFIG_BT_BAP_UNICAST_CLIENT_GROUP_STREAM_COUNT=1
CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SNK_COUNT=1
CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SRC_COUNT=1
CONFIG_BT_GATT_DYNAMIC_DB=y
CONFIG_BT_EXT_ADV=y
CONFIG_BT_DEVICE_NAME="TMAP Central"

View file

@ -0,0 +1,10 @@
sample:
description: Bluetooth Low Energy Audio TMAP Central sample
name: Bluetooth Low Energy Audio TMAP Central sample
tests:
sample.bluetooth.tmap_central:
harness: bluetooth
platform_allow: qemu_cortex_m3 qemu_x86
tags: bluetooth
integration_platforms:
- qemu_cortex_m3

View file

@ -0,0 +1,513 @@
/*
* Copyright (c) 2022-2023 Nordic Semiconductor ASA
* Copyright 2023 NXP
*
* SPDX-License-Identifier: Apache-2.0
*/
#if defined(CONFIG_BT_CAP_INITIATOR)
#include <zephyr/types.h>
#include <stddef.h>
#include <zephyr/kernel.h>
#include <zephyr/bluetooth/bluetooth.h>
#include <zephyr/bluetooth/conn.h>
#include <zephyr/bluetooth/audio/audio.h>
#include <zephyr/bluetooth/audio/bap_lc3_preset.h>
#include <zephyr/bluetooth/audio/cap.h>
#include <zephyr/bluetooth/audio/bap.h>
#include "common.h"
static struct k_work_delayable audio_send_work;
static struct bt_cap_stream unicast_streams[CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SNK_COUNT +
CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SRC_COUNT];
static struct bt_bap_ep *unicast_sink_eps[CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SNK_COUNT];
static struct bt_bap_ep *unicast_source_eps[CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SRC_COUNT];
NET_BUF_POOL_FIXED_DEFINE(tx_pool, CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SNK_COUNT,
CONFIG_BT_ISO_TX_MTU + BT_ISO_CHAN_SEND_RESERVE, 8, NULL);
static K_SEM_DEFINE(sem_cas_discovery, 0, 1);
static K_SEM_DEFINE(sem_discover_sink, 0, 1);
static K_SEM_DEFINE(sem_discover_source, 0, 1);
static K_SEM_DEFINE(sem_audio_start, 0, 1);
static void unicast_stream_configured(struct bt_bap_stream *stream,
const struct bt_codec_qos_pref *pref)
{
printk("Configured stream %p\n", stream);
/* TODO: The preference should be used/taken into account when
* setting the QoS
*/
}
static void unicast_stream_qos_set(struct bt_bap_stream *stream)
{
printk("QoS set stream %p\n", stream);
}
static void unicast_stream_enabled(struct bt_bap_stream *stream)
{
printk("Enabled stream %p\n", stream);
}
static void unicast_stream_started(struct bt_bap_stream *stream)
{
printk("Started stream %p\n", stream);
k_sem_give(&sem_audio_start);
}
static void unicast_stream_metadata_updated(struct bt_bap_stream *stream)
{
printk("Metadata updated stream %p\n", stream);
}
static void unicast_stream_disabled(struct bt_bap_stream *stream)
{
printk("Disabled stream %p\n", stream);
}
static void unicast_stream_stopped(struct bt_bap_stream *stream, uint8_t reason)
{
printk("Stopped stream %p with reason 0x%02X\n", stream, reason);
/* Stop send timer */
k_work_cancel_delayable(&audio_send_work);
}
static void unicast_stream_released(struct bt_bap_stream *stream)
{
printk("Released stream %p\n", stream);
}
static struct bt_bap_stream_ops unicast_stream_ops = {
.configured = unicast_stream_configured,
.qos_set = unicast_stream_qos_set,
.enabled = unicast_stream_enabled,
.started = unicast_stream_started,
.metadata_updated = unicast_stream_metadata_updated,
.disabled = unicast_stream_disabled,
.stopped = unicast_stream_stopped,
.released = unicast_stream_released,
};
static struct bt_bap_lc3_preset unicast_preset_48_2_1 =
BT_BAP_LC3_UNICAST_PRESET_48_2_1(BT_AUDIO_LOCATION_FRONT_LEFT,
BT_AUDIO_CONTEXT_TYPE_MEDIA);
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) {
printk("Failed to discover CAS: %d", err);
return;
}
if (IS_ENABLED(CONFIG_BT_CAP_ACCEPTOR_SET_MEMBER)) {
if (csis_inst == NULL) {
printk("Failed to discover CAS CSIS");
return;
}
printk("Found CAS with CSIS %p\n", csis_inst);
} else {
printk("Found CAS\n");
}
k_sem_give(&sem_cas_discovery);
}
static void unicast_start_complete_cb(struct bt_bap_unicast_group *unicast_group,
int err, struct bt_conn *conn)
{
if (err != 0) {
printk("Failed to start (failing conn %p): %d", conn, err);
return;
}
k_sem_give(&sem_audio_start);
}
static void unicast_update_complete_cb(int err, struct bt_conn *conn)
{
if (err != 0) {
printk("Failed to update (failing conn %p): %d", conn, err);
return;
}
}
static void unicast_stop_complete_cb(struct bt_bap_unicast_group *unicast_group, int err,
struct bt_conn *conn)
{
if (err != 0) {
printk("Failed to stop (failing conn %p): %d", conn, err);
return;
}
}
static struct bt_cap_initiator_cb cap_cb = {
.unicast_discovery_complete = cap_discovery_complete_cb,
.unicast_start_complete = unicast_start_complete_cb,
.unicast_update_complete = unicast_update_complete_cb,
.unicast_stop_complete = unicast_stop_complete_cb,
};
static int discover_cas(struct bt_conn *conn)
{
int err;
err = bt_cap_initiator_unicast_discover(conn);
if (err != 0) {
printk("Failed to discover CAS: %d\n", err);
return err;
}
err = k_sem_take(&sem_cas_discovery, K_FOREVER);
if (err != 0) {
printk("failed to take sem_cas_discovery (err %d)\n", err);
return err;
}
return err;
}
static void print_hex(const uint8_t *ptr, size_t len)
{
while (len-- != 0) {
printk("%02x", *ptr++);
}
}
static void print_codec_capabilities(const struct bt_codec *codec)
{
printk("codec 0x%02x cid 0x%04x vid 0x%04x count %u\n",
codec->id, codec->cid, codec->vid, codec->data_count);
for (size_t i = 0; i < codec->data_count; i++) {
printk("data #%zu: type 0x%02x len %u\n",
i, codec->data[i].data.type,
codec->data[i].data.data_len);
print_hex(codec->data[i].data.data,
codec->data[i].data.data_len -
sizeof(codec->data[i].data.type));
printk("\n");
}
for (size_t i = 0; i < codec->meta_count; i++) {
printk("meta #%zu: type 0x%02x len %u\n",
i, codec->meta[i].data.type,
codec->meta[i].data.data_len);
print_hex(codec->meta[i].data.data,
codec->meta[i].data.data_len -
sizeof(codec->meta[i].data.type));
printk("\n");
}
}
static void print_remote_codec(const struct bt_codec *codec_capabilities, int index,
enum bt_audio_dir dir)
{
printk("#%u: codec_capabilities %p dir 0x%02x\n", index, codec_capabilities, dir);
print_codec_capabilities(codec_capabilities);
}
static void add_remote_sink(struct bt_bap_ep *ep, uint8_t index)
{
printk("Sink #%u: ep %p\n", index, ep);
if (index > ARRAY_SIZE(unicast_sink_eps)) {
printk("Could not add sink ep[%u]\n", index);
return;
}
unicast_sink_eps[index] = ep;
}
static void add_remote_source(struct bt_bap_ep *ep, uint8_t index)
{
printk("Source #%u: ep %p\n", index, ep);
if (index > ARRAY_SIZE(unicast_source_eps)) {
printk("Could not add source ep[%u]\n", index);
return;
}
unicast_source_eps[index] = ep;
}
static void discover_sink_cb(struct bt_conn *conn, struct bt_codec *codec, struct bt_bap_ep *ep,
struct bt_bap_unicast_client_discover_params *params)
{
static bool codec_found;
static bool endpoint_found;
if (params->err != 0) {
printk("Discovery failed: %d\n", params->err);
return;
}
if (codec != NULL) {
print_remote_codec(codec, params->num_caps, params->dir);
codec_found = true;
return;
}
if (ep != NULL) {
if (params->dir == BT_AUDIO_DIR_SINK) {
add_remote_sink(ep, params->num_eps);
endpoint_found = true;
} else {
printk("Invalid param dir: %u\n", params->dir);
}
return;
}
printk("Sink discover complete\n");
(void)memset(params, 0, sizeof(*params));
if (endpoint_found && codec_found) {
k_sem_give(&sem_discover_sink);
} else {
printk("Did not discover endpoint and codec\n");
}
}
static int discover_sinks(struct bt_conn *conn)
{
int err = 0;
static struct bt_bap_unicast_client_discover_params params;
params.func = discover_sink_cb;
params.dir = BT_AUDIO_DIR_SINK;
err = bt_bap_unicast_client_discover(conn, &params);
if (err != 0) {
printk("Failed to discover sink: %d\n", err);
return err;
}
err = k_sem_take(&sem_discover_sink, K_FOREVER);
if (err != 0) {
printk("failed to take sem_discover_sink (err %d)\n", err);
return err;
}
return err;
}
static void discover_sources_cb(struct bt_conn *conn, struct bt_codec *codec, struct bt_bap_ep *ep,
struct bt_bap_unicast_client_discover_params *params)
{
if (params->err != 0 && params->err != BT_ATT_ERR_ATTRIBUTE_NOT_FOUND) {
printk("Discovery failed: %d\n", params->err);
return;
}
if (codec != NULL) {
print_remote_codec(codec, params->num_caps, params->dir);
return;
}
if (ep != NULL) {
add_remote_source(ep, params->num_eps);
return;
}
if (params->err == BT_ATT_ERR_ATTRIBUTE_NOT_FOUND) {
printk("Discover sinks completed without finding any source ASEs\n");
} else {
printk("Discover sources complete: err %d\n", params->err);
}
(void)memset(params, 0, sizeof(*params));
k_sem_give(&sem_discover_source);
}
static int discover_sources(struct bt_conn *conn)
{
static struct bt_bap_unicast_client_discover_params params;
int err;
params.func = discover_sources_cb;
params.dir = BT_AUDIO_DIR_SOURCE;
err = bt_bap_unicast_client_discover(conn, &params);
if (err != 0) {
printk("Failed to discover sources: %d\n", err);
return err;
}
err = k_sem_take(&sem_discover_source, K_FOREVER);
if (err != 0) {
printk("failed to take sem_discover_source (err %d)\n", err);
return err;
}
return 0;
}
static int unicast_group_create(struct bt_bap_unicast_group **out_unicast_group)
{
int err = 0;
struct bt_bap_unicast_group_stream_param group_stream_params;
struct bt_bap_unicast_group_stream_pair_param pair_params;
struct bt_bap_unicast_group_param group_param;
group_stream_params.qos = &unicast_preset_48_2_1.qos;
group_stream_params.stream = &unicast_streams[0].bap_stream;
pair_params.tx_param = &group_stream_params;
pair_params.rx_param = NULL;
group_param.packing = BT_ISO_PACKING_SEQUENTIAL;
group_param.params_count = 1;
group_param.params = &pair_params;
err = bt_bap_unicast_group_create(&group_param, out_unicast_group);
if (err != 0) {
printk("Failed to create group: %d\n", err);
return err;
}
printk("Created group\n");
return err;
}
static int unicast_audio_start(struct bt_conn *conn, struct bt_bap_unicast_group *unicast_group)
{
int err = 0;
struct bt_cap_unicast_audio_start_stream_param stream_param;
struct bt_cap_unicast_audio_start_param param;
param.type = BT_CAP_SET_TYPE_AD_HOC;
param.count = 1u;
param.stream_params = &stream_param;
stream_param.member.member = conn;
stream_param.stream = &unicast_streams[0];
stream_param.ep = unicast_sink_eps[0];
stream_param.codec = &unicast_preset_48_2_1.codec;
stream_param.qos = &unicast_preset_48_2_1.qos;
err = bt_cap_initiator_unicast_audio_start(&param, unicast_group);
if (err != 0) {
printk("Failed to start unicast audio: %d\n", err);
return err;
}
return err;
}
/**
* @brief Send audio data on timeout
*
* This will send an amount of data equal to the configured QoS SDU.
* The data is just mock data, and does not actually represent any audio.
*
* @param work Pointer to the work structure
*/
static void audio_timer_timeout(struct k_work *work)
{
static uint8_t buf_data[CONFIG_BT_ISO_TX_MTU];
static bool data_initialized;
struct net_buf *buf;
struct net_buf *buf_to_send;
int ret;
static size_t len_to_send;
struct bt_bap_stream *stream = &unicast_streams[0].bap_stream;
len_to_send = unicast_preset_48_2_1.qos.sdu;
if (!data_initialized) {
/* TODO: Actually encode some audio data */
for (size_t i = 0; i < ARRAY_SIZE(buf_data); i++) {
buf_data[i] = (uint8_t)i;
}
data_initialized = true;
}
buf = net_buf_alloc(&tx_pool, K_FOREVER);
net_buf_reserve(buf, BT_ISO_CHAN_SEND_RESERVE);
net_buf_add_mem(buf, buf_data, len_to_send);
buf_to_send = buf;
ret = bt_bap_stream_send(stream, buf_to_send, 0, BT_ISO_TIMESTAMP_NONE);
if (ret < 0) {
printk("Failed to send audio data on streams: (%d)\n", ret);
net_buf_unref(buf_to_send);
} else {
printk("Sending mock data with len %zu\n", len_to_send);
}
k_work_schedule(&audio_send_work, K_MSEC(1000));
}
int cap_initiator_init(void)
{
int err = 0;
if (IS_ENABLED(CONFIG_BT_BAP_UNICAST_CLIENT)) {
err = bt_cap_initiator_register_cb(&cap_cb);
if (err != 0) {
printk("Failed to register CAP callbacks (err %d)\n", err);
return err;
}
for (size_t i = 0U; i < ARRAY_SIZE(unicast_streams); i++) {
bt_cap_stream_ops_register(&unicast_streams[i],
&unicast_stream_ops);
}
k_work_init_delayable(&audio_send_work, audio_timer_timeout);
}
return 0;
}
int cap_initiator_setup(struct bt_conn *conn)
{
int err = 0;
struct bt_bap_unicast_group *unicast_group;
k_sem_reset(&sem_cas_discovery);
k_sem_reset(&sem_discover_sink);
k_sem_reset(&sem_discover_source);
k_sem_reset(&sem_audio_start);
err = discover_cas(conn);
if (err != 0) {
return err;
}
err = discover_sinks(conn);
if (err != 0) {
return err;
}
err = discover_sources(conn);
if (err != 0) {
return err;
}
err = unicast_group_create(&unicast_group);
if (err != 0) {
return err;
}
err = unicast_audio_start(conn, unicast_group);
if (err != 0) {
return err;
}
k_sem_take(&sem_audio_start, K_FOREVER);
k_work_schedule(&audio_send_work, K_MSEC(0));
return err;
}
#endif /* CONFIG_BT_CAP_INITIATOR */

View file

@ -0,0 +1,54 @@
/** @file
* @brief Bluetooth Call Control Profile (CCP) Server role.
*
* Copyright 2023 NXP
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/kernel.h>
#include <zephyr/sys/printk.h>
#include <stdlib.h>
#include <stdio.h>
#include <zephyr/bluetooth/conn.h>
#include <zephyr/bluetooth/audio/tbs.h>
#define URI_LIST_LEN 2
static const char *uri_list[URI_LIST_LEN] = {"skype", "tel"};
static bool tbs_originate_call_cb(struct bt_conn *conn, uint8_t call_index,
const char *caller_id)
{
printk("CCP: Placing call to remote with id %u to %s\n",
call_index, caller_id);
return true;
}
static void tbs_terminate_call_cb(struct bt_conn *conn, uint8_t call_index, uint8_t reason)
{
printk("CCP: Call terminated for id %u with reason %u\n",
call_index, reason);
}
static struct bt_tbs_cb tbs_cbs = {
.originate_call = tbs_originate_call_cb,
.terminate_call = tbs_terminate_call_cb,
.hold_call = NULL,
.accept_call = NULL,
.retrieve_call = NULL,
.join_calls = NULL,
.authorize = NULL,
};
int ccp_server_init(void)
{
int err;
bt_tbs_register_cb(&tbs_cbs);
err = bt_tbs_set_uri_scheme_list(0, (const char **)&uri_list, URI_LIST_LEN);
return err;
}

View file

@ -0,0 +1,334 @@
/*
* Copyright 2023 NXP
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/types.h>
#include <stddef.h>
#include <errno.h>
#include <zephyr/kernel.h>
#include <zephyr/sys/byteorder.h>
#include <zephyr/sys/printk.h>
#include <zephyr/bluetooth/bluetooth.h>
#include <zephyr/bluetooth/conn.h>
#include <zephyr/bluetooth/audio/audio.h>
#include <zephyr/bluetooth/audio/bap.h>
#include <zephyr/bluetooth/audio/bap_lc3_preset.h>
#include <zephyr/bluetooth/audio/tmap.h>
#include <zephyr/bluetooth/audio/cap.h>
#include "tmap_central.h"
static struct bt_conn *default_conn;
static K_SEM_DEFINE(sem_connected, 0, 1);
static K_SEM_DEFINE(sem_security_updated, 0, 1);
static K_SEM_DEFINE(sem_disconnected, 0, 1);
static K_SEM_DEFINE(sem_mtu_exchanged, 0, 1);
static K_SEM_DEFINE(sem_discovery_done, 0, 1);
static void att_mtu_updated(struct bt_conn *conn, uint16_t tx, uint16_t rx)
{
printk("MTU exchanged: %u/%u\n", tx, rx);
k_sem_give(&sem_mtu_exchanged);
}
static struct bt_gatt_cb gatt_callbacks = {
.att_mtu_updated = att_mtu_updated
};
void tmap_discovery_complete(enum bt_tmap_role role, struct bt_conn *conn, int err)
{
if (conn != default_conn) {
return;
}
if (err) {
printk("TMAS discovery failed! (err %d)\n", err);
return;
}
printk("TMAS discovery done\n");
k_sem_give(&sem_discovery_done);
}
static struct bt_tmap_cb tmap_callbacks = {
.discovery_complete = tmap_discovery_complete
};
static void start_scan(void);
static int init(void)
{
int err;
err = bt_enable(NULL);
if (err != 0) {
printk("Bluetooth enable failed (err %d)\n", err);
return err;
}
printk("Bluetooth initialized\n");
bt_gatt_cb_register(&gatt_callbacks);
return 0;
}
static void connected(struct bt_conn *conn, uint8_t err)
{
char addr[BT_ADDR_LE_STR_LEN];
(void)bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr));
if (err != 0) {
printk("Failed to connect to %s (%u)\n", addr, err);
bt_conn_unref(default_conn);
default_conn = NULL;
start_scan();
return;
}
if (conn != default_conn) {
return;
}
printk("Connected: %s\n", addr);
k_sem_give(&sem_connected);
}
static void disconnected(struct bt_conn *conn, uint8_t reason)
{
char addr[BT_ADDR_LE_STR_LEN];
if (conn != default_conn) {
return;
}
(void)bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr));
printk("Disconnected: %s (reason 0x%02x)\n", addr, reason);
bt_conn_unref(default_conn);
default_conn = NULL;
k_sem_give(&sem_disconnected);
}
static void security_changed(struct bt_conn *conn, bt_security_t level,
enum bt_security_err err)
{
if (err == 0) {
printk("Security changed: %u\n", err);
k_sem_give(&sem_security_updated);
} else {
printk("Failed to set security level: %u\n", err);
}
}
BT_CONN_CB_DEFINE(conn_callbacks) = {
.connected = connected,
.disconnected = disconnected,
.security_changed = security_changed
};
static bool check_audio_support_and_connect(struct bt_data *data, void *user_data)
{
bt_addr_le_t *addr = user_data;
struct net_buf_simple tmas_svc_data;
struct bt_uuid *uuid;
uint16_t uuid_val;
uint16_t peer_tmap_role = 0;
int err;
printk("[AD]: %u data_len %u\n", data->type, data->data_len);
if (data->type != BT_DATA_SVC_DATA16) {
return true; /* Continue parsing to next AD data type */
}
if (data->data_len < sizeof(uuid_val)) {
printk("AD invalid size %u\n", data->data_len);
return true; /* Continue parsing to next AD data type */
}
net_buf_simple_init_with_data(&tmas_svc_data, (void *)data->data, data->data_len);
uuid_val = net_buf_simple_pull_le16(&tmas_svc_data);
uuid = BT_UUID_DECLARE_16(sys_le16_to_cpu(uuid_val));
if (bt_uuid_cmp(uuid, BT_UUID_TMAS) != 0) {
/* We are looking for the TMAS service data */
return true; /* Continue parsing to next AD data type */
}
printk("Found TMAS in peer adv data!\n");
if (tmas_svc_data.len < sizeof(peer_tmap_role)) {
printk("AD invalid size %u\n", data->data_len);
return false; /* Stop parsing */
}
peer_tmap_role = net_buf_simple_pull_le16(&tmas_svc_data);
if (!(sys_le16_to_cpu(peer_tmap_role) & BT_TMAP_ROLE_UMR)) {
printk("No TMAS UMR support!\n");
return false; /* Stop parsing */
}
printk("Attempt to connect!\n");
err = bt_le_scan_stop();
if (err != 0) {
printk("Failed to stop scan: %d\n", err);
return false;
}
err = bt_conn_le_create(addr, BT_CONN_LE_CREATE_CONN,
BT_LE_CONN_PARAM_DEFAULT,
&default_conn);
if (err != 0) {
printk("Create conn to failed (%u)\n", err);
start_scan();
}
return false; /* Stop parsing */
}
static void scan_recv(const struct bt_le_scan_recv_info *info,
struct net_buf_simple *buf)
{
char le_addr[BT_ADDR_LE_STR_LEN];
if (default_conn != NULL) {
/* Already connected */
return;
}
/* Check for connectable, extended advertising */
if (((info->adv_props & BT_GAP_ADV_PROP_EXT_ADV) != 0) ||
((info->adv_props & BT_GAP_ADV_PROP_CONNECTABLE)) != 0) {
bt_addr_le_to_str(info->addr, le_addr, sizeof(le_addr));
printk("[DEVICE]: %s, ", le_addr);
/* Check for TMAS support in advertising data */
bt_data_parse(buf, check_audio_support_and_connect, (void *)info->addr);
}
}
static struct bt_le_scan_cb scan_callbacks = {
.recv = scan_recv,
};
static void start_scan(void)
{
int err;
err = bt_le_scan_start(BT_LE_SCAN_PASSIVE, NULL);
if (err != 0) {
printk("Scanning failed to start (err %d)\n", err);
return;
}
printk("Scanning successfully started\n");
}
static int scan_and_connect(void)
{
int err;
start_scan();
err = k_sem_take(&sem_connected, K_FOREVER);
if (err != 0) {
printk("failed to take sem_connected (err %d)\n", err);
return err;
}
err = k_sem_take(&sem_mtu_exchanged, K_FOREVER);
if (err != 0) {
printk("failed to take sem_mtu_exchanged (err %d)\n", err);
return err;
}
err = bt_conn_set_security(default_conn, BT_SECURITY_L2);
if (err != 0) {
printk("failed to set security (err %d)\n", err);
return err;
}
err = k_sem_take(&sem_security_updated, K_FOREVER);
if (err != 0) {
printk("failed to take sem_security_updated (err %d)\n", err);
return err;
}
return 0;
}
void main(void)
{
int err;
err = init();
if (err != 0) {
return;
}
printk("Initializing TMAP and setting role\n");
/* Initialize TMAP */
err = bt_tmap_register(BT_TMAP_ROLE_CG | BT_TMAP_ROLE_UMS);
if (err != 0) {
return;
}
/* Initialize CAP Initiator */
err = cap_initiator_init();
if (err != 0) {
return;
}
printk("CAP initialized\n");
/* Initialize VCP Volume Controller */
err = vcp_vol_ctlr_init();
if (err != 0) {
return;
}
printk("VCP initialized\n");
/* Initialize MCP Server */
err = mcp_server_init();
if (err != 0) {
return;
}
printk("MCP initialized\n");
/* Initialize CCP Server */
err = ccp_server_init();
if (err != 0) {
return;
}
printk("CCP initialized\n");
/* Register scan callback and start scanning */
bt_le_scan_cb_register(&scan_callbacks);
err = scan_and_connect();
if (err != 0) {
return;
}
err = bt_tmap_discover(default_conn, &tmap_callbacks);
if (err != 0) {
return;
}
k_sem_take(&sem_discovery_done, K_FOREVER);
/* Send a VCP command */
err = vcp_vol_ctlr_mute();
if (err != 0) {
printk("Error sending mute command!\n");
}
/* Discover and configure unicast streams */
err = cap_initiator_setup(default_conn);
if (err != 0) {
return;
}
}

View file

@ -0,0 +1,24 @@
/** @file
* @brief Bluetooth Media Control Profile (MCP) Server role.
*
* Copyright 2023 NXP
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/kernel.h>
#include <zephyr/sys/printk.h>
#include <stdlib.h>
#include <stdio.h>
#include <zephyr/bluetooth/conn.h>
#include <zephyr/bluetooth/audio/media_proxy.h>
int mcp_server_init(void)
{
int err;
err = media_proxy_pl_init();
return err;
}

View file

@ -0,0 +1,63 @@
/*
* Copyright 2023 NXP
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/types.h>
/**
* @brief Initialize the MCP Server role
*
* @return 0 if success, errno on failure.
*/
int mcp_server_init(void);
/**
* @brief Initialize the CCP Server role
*
* @return 0 if success, errno on failure.
*/
int ccp_server_init(void);
/**
* @brief Initialize the VCP Volume Controller role
*
* @return 0 if success, errno on failure.
*/
int vcp_vol_ctlr_init(void);
/**
* @brief Send the Mute command to the VCP Volume Renderer
*
* @return 0 if success, errno on failure.
*/
int vcp_vol_ctlr_mute(void);
/**
* @brief Send the Unmute command to the VCP Volume Renderer
*
* @return 0 if success, errno on failure.
*/
int vcp_vol_ctlr_unmute(void);
/**
* @brief Set the volume for the VCP Volume Renderer
*
* @return 0 if success, errno on failure.
*/
int vcp_vol_ctlr_set_vol(uint8_t volume);
/**
* @brief Initialize the CAP Initiator role
*
* @return 0 if success, errno on failure.
*/
int cap_initiator_init(void);
/**
* @brief Setup streams for CAP Initiator
*
* @return 0 if success, errno on failure.
*/
int cap_initiator_setup(struct bt_conn *conn);

View file

@ -0,0 +1,143 @@
/*
* Copyright 2023 NXP
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/bluetooth/bluetooth.h>
#include <zephyr/bluetooth/audio/audio.h>
#include <zephyr/bluetooth/audio/vcp.h>
static struct bt_vcp_vol_ctlr *vol_ctlr;
static void vcs_discover_cb(struct bt_vcp_vol_ctlr *vol_ctlr, int err,
uint8_t vocs_count, uint8_t aics_count)
{
if (err != 0) {
printk("VCP: Service could not be discovered (%d)\n", err);
return;
}
}
static void vcs_write_cb(struct bt_vcp_vol_ctlr *vol_ctlr, int err)
{
if (err != 0) {
printk("VCP: Write failed (%d)\n", err);
return;
}
}
static void vcs_state_cb(struct bt_vcp_vol_ctlr *vol_ctlr, int err,
uint8_t volume, uint8_t mute)
{
if (err != 0) {
printk("VCP: state cb err (%d)", err);
return;
}
printk("VCS volume %u, mute %u\n", volume, mute);
}
static void vcs_flags_cb(struct bt_vcp_vol_ctlr *vol_ctlr, int err,
uint8_t flags)
{
if (err != 0) {
printk("VCP: flags cb err (%d)", err);
return;
}
printk("VCS flags 0x%02X\n", flags);
}
static struct bt_vcp_vol_ctlr_cb vcp_cbs = {
.discover = vcs_discover_cb,
.vol_down = vcs_write_cb,
.vol_up = vcs_write_cb,
.mute = vcs_write_cb,
.unmute = vcs_write_cb,
.vol_down_unmute = vcs_write_cb,
.vol_up_unmute = vcs_write_cb,
.vol_set = vcs_write_cb,
.state = vcs_state_cb,
.flags = vcs_flags_cb,
};
static int process_profile_connection(struct bt_conn *conn)
{
int err = 0;
err = bt_vcp_vol_ctlr_discover(conn, &vol_ctlr);
if (err != 0) {
printk("bt_vcp_vol_ctlr_discover (err %d)\n", err);
}
return err;
}
static void connected(struct bt_conn *conn, uint8_t err)
{
if (err) {
printk("Connection failed (err %d)\n", err);
return;
}
if (process_profile_connection(conn) != 0) {
printk("Profile connection failed");
}
}
BT_CONN_CB_DEFINE(conn_callbacks) = {
.connected = connected,
};
int vcp_vol_ctlr_init(void)
{
int err;
err = bt_vcp_vol_ctlr_cb_register(&vcp_cbs);
if (err != 0) {
printk("CB register failed (err %d)\n", err);
}
return err;
}
int vcp_vol_ctlr_mute(void)
{
int err;
if (vol_ctlr != NULL) {
err = bt_vcp_vol_ctlr_mute(vol_ctlr);
} else {
err = -EINVAL;
}
return err;
}
int vcp_vol_ctlr_unmute(void)
{
int err;
if (vol_ctlr != NULL) {
err = bt_vcp_vol_ctlr_unmute(vol_ctlr);
} else {
err = -EINVAL;
}
return err;
}
int vcp_vol_ctlr_set_vol(uint8_t volume)
{
int err;
if (vol_ctlr != NULL) {
err = bt_vcp_vol_ctlr_set_vol(vol_ctlr, volume);
} else {
err = -EINVAL;
}
return err;
}

View file

@ -0,0 +1,19 @@
# SPDX-License-Identifier: Apache-2.0
cmake_minimum_required(VERSION 3.20.0)
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
project(tmap_peripheral)
target_sources(app PRIVATE
src/main.c
src/vcp_vol_renderer.c
src/bap_unicast_sr.c
src/ccp_call_ctrl.c
src/mcp_ctlr.c
)
target_sources_ifdef(CONFIG_TMAP_PERIPHERAL_DUO app PRIVATE
src/csip_set_member.c
)
zephyr_library_include_directories(${ZEPHYR_BASE}/samples/bluetooth)

View file

@ -0,0 +1,50 @@
#
# Copyright (c) 2022 Codecoup
# Copyright 2023 NXP
#
# SPDX-License-Identifier: Apache-2.0
#
mainmenu "Bluetooth: Earbuds"
menu "Zephyr"
source "Kconfig.zephyr"
endmenu
menu "Earbuds"
config TMAP_PERIPHERAL_SET_RANK
int "Device rank in set"
depends on TMAP_PERIPHERAL_DUO
range 1 2
help
Rank of this device in set.
choice TMAP_PERIPHERAL_TYPE_CHOICE
prompt "Earbuds type"
help
Select the Earbuds Type to compile.
config TMAP_PERIPHERAL_SINGLE
bool "Single ear headset"
config TMAP_PERIPHERAL_DUO
depends on BT_CAP_ACCEPTOR_SET_MEMBER
bool "Duo headset"
endchoice # TMAP_PERIPHERAL_TYPE_CHOICE
choice TMAP_PERIPHERAL_LOCATION
prompt "Earbud Location"
help
Select the Earbud location.
config TMAP_PERIPHERAL_LEFT
bool "Left Ear"
config TMAP_PERIPHERAL_RIGHT
bool "Right Ear"
endchoice # TMAP_PERIPHERAL_LOCATION
endmenu

View file

@ -0,0 +1,22 @@
.. _bluetooth_tmap_peripheral:
Bluetooth: TMAP Peripheral
##########################
Overview
********
Application demonstrating the LE Audio TMAP peripheral functionality. Implements the CT and UMR roles.
Requirements
************
* A board with Bluetooth Low Energy 5.2 support
Building and Running
********************
This sample can be found under
:zephyr_file:`samples/bluetooth/tmap_peripheral` in the Zephyr tree.
See :ref:`bluetooth samples section <bluetooth-samples>` for details.

View file

@ -0,0 +1,10 @@
CONFIG_LOG_MODE_IMMEDIATE=y
CONFIG_BT_TINYCRYPT_ECC=y
CONFIG_LIBLC3=y
CONFIG_FPU=y
# For LE-audio at 10ms intervals we need the tick counter to occur more frequently
# than every 10 ms as each PDU for some reason takes 2 ticks to process.
CONFIG_SYS_CLOCK_TICKS_PER_SEC=1000
CONFIG_NATIVE_POSIX_SLOWDOWN_TO_REAL_TIME=y

View file

@ -0,0 +1,4 @@
CONFIG_BT_CTLR_PERIPHERAL_ISO=y
# Supports the highest SDU size required by any BAP LC3 presets (155)
CONFIG_BT_CTLR_ISO_TX_BUFFER_SIZE=155

View file

@ -0,0 +1,8 @@
# For LC3 the following configs are needed
CONFIG_FPU=y
CONFIG_LIBLC3=y
# The LC3 codec uses a large amount of stack. This app runs the codec in the work-queue, hence
# inctease stack size for that thread.
CONFIG_SYSTEM_WORKQUEUE_STACK_SIZE=4096
# LC3 lib requires floating point support in the c-lib NEWLIB is one way of getting that.
CONFIG_NEWLIB_LIBC=y

View file

@ -0,0 +1,8 @@
# For LC3 the following configs are needed
CONFIG_FPU=y
CONFIG_LIBLC3=y
# The LC3 codec uses a large amount of stack. This app runs the codec in the work-queue, hence
# inctease stack size for that thread.
CONFIG_SYSTEM_WORKQUEUE_STACK_SIZE=4096
# LC3 lib requires floating point support in the c-lib NEWLIB is one way of getting that.
CONFIG_NEWLIB_LIBC=y

View file

@ -0,0 +1,3 @@
CONFIG_BT_CSIP_SET_MEMBER=y
CONFIG_TMAP_PERIPHERAL_DUO=y
CONFIG_BT_CAP_ACCEPTOR_SET_MEMBER=y

View file

@ -0,0 +1,55 @@
CONFIG_BT=y
CONFIG_BT_DEBUG_LOG=y
CONFIG_BT_PERIPHERAL=y
CONFIG_BT_PRIVACY=y
CONFIG_BT_AUDIO=y
CONFIG_UTF8=y
CONFIG_BT_SMP=y
CONFIG_BT_KEYS_OVERWRITE_OLDEST=y
CONFIG_BT_L2CAP_TX_BUF_COUNT=20
# TMAP support
CONFIG_BT_TMAP=y
# CAP
CONFIG_BT_CAP_ACCEPTOR=y
# BAP support
CONFIG_BT_BAP_UNICAST_SERVER=y
# VCP support
CONFIG_BT_VCP_VOL_REND=y
# MCP support
CONFIG_BT_MCC=y
# Support an ISO channel per ASE
CONFIG_BT_ASCS_ASE_SNK_COUNT=1
CONFIG_BT_ASCS_ASE_SRC_COUNT=1
# Support an ISO channel per ASE
CONFIG_BT_ISO_MAX_CHAN=2
# Sink Contexts Supported: Unspecified, Conversational, Media
CONFIG_BT_PACS_SNK_CONTEXT=0x0007
# Source Contexts Supported: Unspecified, Conversational, Media
CONFIG_BT_PACS_SRC_CONTEXT=0x0007
# Sink PAC Location Support
CONFIG_BT_PAC_SNK_LOC=y
# Source PAC Location Support
CONFIG_BT_PAC_SRC_LOC=y
# CCP Client Support
CONFIG_BT_TBS_CLIENT=y
CONFIG_BT_TBS_CLIENT_GTBS=y
CONFIG_BT_TBS_CLIENT_MAX_TBS_INSTANCES=0
CONFIG_BT_TBS_CLIENT_ORIGINATE_CALL=y
CONFIG_BT_TBS_CLIENT_TERMINATE_CALL=y
CONFIG_BT_TBS_CLIENT_BEARER_URI_SCHEMES_SUPPORTED_LIST=y
# Generic config
CONFIG_BT_GATT_DYNAMIC_DB=y
CONFIG_BT_GATT_CLIENT=y
CONFIG_BT_EXT_ADV=y
CONFIG_BT_DEVICE_NAME="TMAP Peripheral"

View file

@ -0,0 +1,19 @@
sample:
description: Bluetooth Low Energy Audio TMAP Peripheral sample
name: Bluetooth Low Energy Audio TMAP Peripheral sample
tests:
sample.bluetooth.tmap_peripheral:
harness: bluetooth
platform_allow: qemu_cortex_m3 qemu_x86
tags: bluetooth
integration_platforms:
- qemu_cortex_m3
sample.bluetooth.tmap_peripheral.duo:
harness: bluetooth
platform_allow: qemu_cortex_m3 qemu_x86
tags: bluetooth
extra_args: OVERLAY_CONFIG="duo.conf"
extra_configs:
- CONFIG_TMAP_PERIPHERAL_SET_RANK=2
integration_platforms:
- qemu_cortex_m3

View file

@ -0,0 +1,496 @@
/** @file
* @brief Bluetooth Basic Audio Profile (BAP) Unicast Server role.
*
* Copyright (c) 2021-2023 Nordic Semiconductor ASA
* Copyright (c) 2022 Codecoup
* Copyright (c) 2023 NXP
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/kernel.h>
#include <zephyr/sys/byteorder.h>
#include <zephyr/sys/printk.h>
#include <zephyr/bluetooth/bluetooth.h>
#include <zephyr/bluetooth/conn.h>
#include <zephyr/bluetooth/audio/audio.h>
#include <zephyr/bluetooth/audio/bap_lc3_preset.h>
#include <zephyr/bluetooth/audio/bap.h>
#include <zephyr/bluetooth/audio/pacs.h>
#include <zephyr/bluetooth/audio/tbs.h>
#define AVAILABLE_SINK_CONTEXT CONFIG_BT_PACS_SNK_CONTEXT
#define AVAILABLE_SOURCE_CONTEXT CONFIG_BT_PACS_SRC_CONTEXT
static struct bt_bap_lc3_preset codec_cfg_src_16_1_1 =
BT_BAP_LC3_UNICAST_PRESET_16_1_1(BT_AUDIO_LOCATION_FRONT_LEFT,
CONFIG_BT_PACS_SRC_CONTEXT);
static struct bt_bap_lc3_preset codec_cfg_snk_16_1_1 =
BT_BAP_LC3_UNICAST_PRESET_16_1_1(BT_AUDIO_LOCATION_FRONT_LEFT,
CONFIG_BT_PACS_SNK_CONTEXT);
static struct bt_bap_lc3_preset codec_cfg_src_32_1_1 =
BT_BAP_LC3_UNICAST_PRESET_32_1_1(BT_AUDIO_LOCATION_FRONT_LEFT,
CONFIG_BT_PACS_SRC_CONTEXT);
static struct bt_bap_lc3_preset codec_cfg_snk_32_1_1 =
BT_BAP_LC3_UNICAST_PRESET_32_1_1(BT_AUDIO_LOCATION_FRONT_LEFT,
CONFIG_BT_PACS_SNK_CONTEXT);
static struct bt_bap_lc3_preset codec_cfg_src_32_2_1 =
BT_BAP_LC3_UNICAST_PRESET_32_2_1(BT_AUDIO_LOCATION_FRONT_LEFT,
CONFIG_BT_PACS_SRC_CONTEXT);
static struct bt_bap_lc3_preset codec_cfg_snk_32_2_1 =
BT_BAP_LC3_UNICAST_PRESET_32_2_1(BT_AUDIO_LOCATION_FRONT_LEFT,
CONFIG_BT_PACS_SNK_CONTEXT);
static struct bt_bap_lc3_preset codec_cfg_snk_48_1_1 =
BT_BAP_LC3_UNICAST_PRESET_48_1_1(BT_AUDIO_LOCATION_FRONT_LEFT,
CONFIG_BT_PACS_SNK_CONTEXT);
static struct bt_bap_lc3_preset codec_cfg_snk_48_2_1 =
BT_BAP_LC3_UNICAST_PRESET_48_2_1(BT_AUDIO_LOCATION_FRONT_LEFT,
CONFIG_BT_PACS_SNK_CONTEXT);
static struct bt_bap_lc3_preset codec_cfg_snk_48_3_1 =
BT_BAP_LC3_UNICAST_PRESET_48_3_1(BT_AUDIO_LOCATION_FRONT_LEFT,
CONFIG_BT_PACS_SNK_CONTEXT);
static struct bt_bap_lc3_preset codec_cfg_snk_48_4_1 =
BT_BAP_LC3_UNICAST_PRESET_48_4_1(BT_AUDIO_LOCATION_FRONT_LEFT,
CONFIG_BT_PACS_SNK_CONTEXT);
static struct bt_bap_lc3_preset codec_cfg_snk_48_5_1 =
BT_BAP_LC3_UNICAST_PRESET_48_5_1(BT_AUDIO_LOCATION_FRONT_LEFT,
CONFIG_BT_PACS_SNK_CONTEXT);
static struct bt_bap_lc3_preset codec_cfg_snk_48_6_1 =
BT_BAP_LC3_UNICAST_PRESET_48_6_1(BT_AUDIO_LOCATION_FRONT_LEFT,
CONFIG_BT_PACS_SNK_CONTEXT);
static struct bt_conn *default_conn;
static struct bt_bap_stream streams[CONFIG_BT_ASCS_ASE_SNK_COUNT + CONFIG_BT_ASCS_ASE_SRC_COUNT];
static struct audio_source {
struct bt_bap_stream *stream;
uint16_t seq_num;
} source_streams[CONFIG_BT_ASCS_ASE_SRC_COUNT];
static size_t configured_source_stream_count;
static const struct bt_codec_qos_pref qos_pref = BT_CODEC_QOS_PREF(true, BT_GAP_LE_PHY_2M, 0x02,
10, 20000, 40000, 20000, 40000);
static void print_hex(const uint8_t *ptr, size_t len)
{
while (len-- != 0) {
printk("%02x", *ptr++);
}
}
static void print_codec(const struct bt_codec *codec)
{
printk("codec 0x%02x cid 0x%04x vid 0x%04x count %u\n",
codec->id, codec->cid, codec->vid, codec->data_count);
for (size_t i = 0; i < codec->data_count; i++) {
printk("data #%zu: type 0x%02x len %u\n",
i, codec->data[i].data.type,
codec->data[i].data.data_len);
print_hex(codec->data[i].data.data,
codec->data[i].data.data_len -
sizeof(codec->data[i].data.type));
printk("\n");
}
if (codec->id == BT_CODEC_LC3_ID) {
/* LC3 uses the generic LTV format - other codecs might do as well */
uint32_t chan_allocation;
printk(" Frequency: %d Hz\n", bt_codec_cfg_get_freq(codec));
printk(" Frame Duration: %d us\n", bt_codec_cfg_get_frame_duration_us(codec));
if (bt_codec_cfg_get_chan_allocation_val(codec, &chan_allocation) == 0) {
printk(" Channel allocation: 0x%x\n", chan_allocation);
}
printk(" Octets per frame: %d (negative means value not pressent)\n",
bt_codec_cfg_get_octets_per_frame(codec));
printk(" Frames per SDU: %d\n",
bt_codec_cfg_get_frame_blocks_per_sdu(codec, true));
}
for (size_t i = 0; i < codec->meta_count; i++) {
printk("meta #%zu: type 0x%02x len %u\n",
i, codec->meta[i].data.type,
codec->meta[i].data.data_len);
print_hex(codec->meta[i].data.data,
codec->meta[i].data.data_len -
sizeof(codec->meta[i].data.type));
printk("\n");
}
}
static void print_qos(const struct bt_codec_qos *qos)
{
printk("QoS: interval %u framing 0x%02x phy 0x%02x sdu %u "
"rtn %u latency %u pd %u\n",
qos->interval, qos->framing, qos->phy, qos->sdu,
qos->rtn, qos->latency, qos->pd);
}
static struct bt_bap_stream *stream_alloc(void)
{
for (size_t i = 0; i < ARRAY_SIZE(streams); i++) {
struct bt_bap_stream *stream = &streams[i];
if (!stream->conn) {
return stream;
}
}
return NULL;
}
static int lc3_config(struct bt_conn *conn, const struct bt_bap_ep *ep, enum bt_audio_dir dir,
const struct bt_codec *codec, struct bt_bap_stream **stream,
struct bt_codec_qos_pref *const pref, struct bt_bap_ascs_rsp *rsp)
{
printk("ASE Codec Config: conn %p ep %p dir %u\n", conn, ep, dir);
print_codec(codec);
*stream = stream_alloc();
if (*stream == NULL) {
printk("No streams available\n");
*rsp = BT_BAP_ASCS_RSP(BT_BAP_ASCS_RSP_CODE_NO_MEM, BT_BAP_ASCS_REASON_NONE);
return -ENOMEM;
}
printk("ASE Codec Config stream %p\n", *stream);
if (dir == BT_AUDIO_DIR_SOURCE) {
source_streams[configured_source_stream_count++].stream = *stream;
}
*pref = qos_pref;
return 0;
}
static int lc3_reconfig(struct bt_bap_stream *stream, enum bt_audio_dir dir,
const struct bt_codec *codec, struct bt_codec_qos_pref *const pref,
struct bt_bap_ascs_rsp *rsp)
{
printk("ASE Codec Reconfig: stream %p\n", stream);
print_codec(codec);
*pref = qos_pref;
return 0;
}
static int lc3_qos(struct bt_bap_stream *stream, const struct bt_codec_qos *qos,
struct bt_bap_ascs_rsp *rsp)
{
printk("QoS: stream %p qos %p\n", stream, qos);
print_qos(qos);
return 0;
}
static int lc3_enable(struct bt_bap_stream *stream, const struct bt_codec_data *meta,
size_t meta_count, struct bt_bap_ascs_rsp *rsp)
{
printk("Enable: stream %p meta_count %u\n", stream, meta_count);
return 0;
}
static int lc3_start(struct bt_bap_stream *stream, struct bt_bap_ascs_rsp *rsp)
{
printk("Start: stream %p\n", stream);
return 0;
}
static bool valid_metadata_type(uint8_t type, uint8_t len)
{
switch (type) {
case BT_AUDIO_METADATA_TYPE_PREF_CONTEXT:
case BT_AUDIO_METADATA_TYPE_STREAM_CONTEXT:
if (len != 2) {
return false;
}
return true;
case BT_AUDIO_METADATA_TYPE_STREAM_LANG:
if (len != 3) {
return false;
}
return true;
case BT_AUDIO_METADATA_TYPE_PARENTAL_RATING:
if (len != 1) {
return false;
}
return true;
case BT_AUDIO_METADATA_TYPE_EXTENDED: /* 1 - 255 octets */
case BT_AUDIO_METADATA_TYPE_VENDOR: /* 1 - 255 octets */
if (len < 1) {
return false;
}
return true;
case BT_AUDIO_METADATA_TYPE_CCID_LIST: /* 2 - 254 octets */
if (len < 2) {
return false;
}
return true;
case BT_AUDIO_METADATA_TYPE_PROGRAM_INFO: /* 0 - 255 octets */
case BT_AUDIO_METADATA_TYPE_PROGRAM_INFO_URI: /* 0 - 255 octets */
return true;
default:
return false;
}
}
static int lc3_metadata(struct bt_bap_stream *stream, const struct bt_codec_data *meta,
size_t meta_count, struct bt_bap_ascs_rsp *rsp)
{
printk("Metadata: stream %p meta_count %u\n", stream, meta_count);
bool stream_context_present = false;
for (size_t i = 0; i < meta_count; i++) {
const struct bt_codec_data *data = &meta[i];
if (!valid_metadata_type(data->data.type, data->data.data_len)) {
printk("Invalid metadata type %u or length %u\n",
data->data.type, data->data.data_len);
*rsp = BT_BAP_ASCS_RSP(BT_BAP_ASCS_RSP_CODE_METADATA_REJECTED,
data->data.type);
return -EINVAL;
}
if (data->data.type == BT_AUDIO_METADATA_TYPE_STREAM_CONTEXT) {
stream_context_present = true;
}
if (data->data.type == BT_AUDIO_METADATA_TYPE_CCID_LIST) {
for (uint8_t i = 0; i < data->data.data_len; i++) {
const uint8_t ccid = data->data.data[i];
if (!(IS_ENABLED(CONFIG_BT_TBS_CLIENT_CCID) &&
bt_tbs_client_get_by_ccid(default_conn, ccid) != NULL)) {
printk("CCID %u is unknown", ccid);
*rsp = BT_BAP_ASCS_RSP(
BT_BAP_ASCS_RSP_CODE_METADATA_REJECTED,
BT_BAP_ASCS_REASON_NONE);
return -EINVAL;
}
}
}
}
if (stream_context_present == false) {
printk("Stream audio context not present on peer!");
*rsp = BT_BAP_ASCS_RSP(BT_BAP_ASCS_RSP_CODE_METADATA_REJECTED,
BT_BAP_ASCS_REASON_NONE);
return -EINVAL;
}
return 0;
}
static int lc3_disable(struct bt_bap_stream *stream, struct bt_bap_ascs_rsp *rsp)
{
printk("Disable: stream %p\n", stream);
return 0;
}
static int lc3_stop(struct bt_bap_stream *stream, struct bt_bap_ascs_rsp *rsp)
{
printk("Stop: stream %p\n", stream);
return 0;
}
static int lc3_release(struct bt_bap_stream *stream, struct bt_bap_ascs_rsp *rsp)
{
printk("Release: stream %p\n", stream);
return 0;
}
static const struct bt_bap_unicast_server_cb unicast_server_cb = {
.config = lc3_config,
.reconfig = lc3_reconfig,
.qos = lc3_qos,
.enable = lc3_enable,
.start = lc3_start,
.metadata = lc3_metadata,
.disable = lc3_disable,
.stop = lc3_stop,
.release = lc3_release,
};
static void stream_recv(struct bt_bap_stream *stream, const struct bt_iso_recv_info *info,
struct net_buf *buf)
{
if (buf->len != 0) {
printk("Incoming audio on stream %p len %u\n", stream, buf->len);
}
/* TODO: decode data (if applicable) */
}
static void stream_enabled(struct bt_bap_stream *stream)
{
const int err = bt_bap_stream_start(stream);
if (err != 0) {
printk("Failed to start stream %p: %d", stream, err);
}
}
static struct bt_bap_stream_ops stream_ops = {
.recv = stream_recv,
.enabled = stream_enabled
};
static void connected(struct bt_conn *conn, uint8_t err)
{
default_conn = bt_conn_ref(conn);
}
static void disconnected(struct bt_conn *conn, uint8_t reason)
{
if (conn != default_conn) {
return;
}
bt_conn_unref(default_conn);
default_conn = NULL;
if (IS_ENABLED(CONFIG_BT_ASCS_ASE_SRC)) {
/* reset data */
(void)memset(source_streams, 0, sizeof(source_streams));
configured_source_stream_count = 0U;
}
}
BT_CONN_CB_DEFINE(conn_callbacks) = {
.connected = connected,
.disconnected = disconnected,
};
static struct bt_pacs_cap cap_sink_16_1_1 = {
.codec = &codec_cfg_snk_16_1_1.codec,
};
static struct bt_pacs_cap cap_source_16_1_1 = {
.codec = &codec_cfg_src_16_1_1.codec,
};
static struct bt_pacs_cap cap_sink_32_1_1 = {
.codec = &codec_cfg_snk_32_1_1.codec,
};
static struct bt_pacs_cap cap_source_32_1_1 = {
.codec = &codec_cfg_src_32_1_1.codec,
};
static struct bt_pacs_cap cap_sink_32_2_1 = {
.codec = &codec_cfg_snk_32_2_1.codec,
};
static struct bt_pacs_cap cap_source_32_2_1 = {
.codec = &codec_cfg_src_32_2_1.codec,
};
static struct bt_pacs_cap cap_sink_48_1_1 = {
.codec = &codec_cfg_snk_48_1_1.codec,
};
static struct bt_pacs_cap cap_sink_48_2_1 = {
.codec = &codec_cfg_snk_48_2_1.codec,
};
static struct bt_pacs_cap cap_sink_48_3_1 = {
.codec = &codec_cfg_snk_48_3_1.codec,
};
static struct bt_pacs_cap cap_sink_48_4_1 = {
.codec = &codec_cfg_snk_48_4_1.codec,
};
static struct bt_pacs_cap cap_sink_48_5_1 = {
.codec = &codec_cfg_snk_48_5_1.codec,
};
static struct bt_pacs_cap cap_sink_48_6_1 = {
.codec = &codec_cfg_snk_48_6_1.codec,
};
int bap_unicast_sr_init(void)
{
bt_bap_unicast_server_register_cb(&unicast_server_cb);
if (IS_ENABLED(CONFIG_BT_PAC_SNK_LOC)) {
/* Register CT required capabilities */
bt_pacs_cap_register(BT_AUDIO_DIR_SINK, &cap_sink_16_1_1);
bt_pacs_cap_register(BT_AUDIO_DIR_SINK, &cap_sink_32_1_1);
bt_pacs_cap_register(BT_AUDIO_DIR_SINK, &cap_sink_32_2_1);
/* Register UMR required capabilities */
bt_pacs_cap_register(BT_AUDIO_DIR_SINK, &cap_sink_48_1_1);
bt_pacs_cap_register(BT_AUDIO_DIR_SINK, &cap_sink_48_2_1);
bt_pacs_cap_register(BT_AUDIO_DIR_SINK, &cap_sink_48_3_1);
bt_pacs_cap_register(BT_AUDIO_DIR_SINK, &cap_sink_48_4_1);
bt_pacs_cap_register(BT_AUDIO_DIR_SINK, &cap_sink_48_5_1);
bt_pacs_cap_register(BT_AUDIO_DIR_SINK, &cap_sink_48_6_1);
if (IS_ENABLED(CONFIG_TMAP_PERIPHERAL_LEFT)) {
bt_pacs_set_location(BT_AUDIO_DIR_SINK, BT_AUDIO_LOCATION_FRONT_LEFT);
} else {
bt_pacs_set_location(BT_AUDIO_DIR_SINK, BT_AUDIO_LOCATION_FRONT_RIGHT);
}
bt_pacs_set_supported_contexts(BT_AUDIO_DIR_SINK,
AVAILABLE_SINK_CONTEXT);
bt_pacs_set_available_contexts(BT_AUDIO_DIR_SINK,
AVAILABLE_SINK_CONTEXT);
}
if (IS_ENABLED(CONFIG_BT_ASCS_ASE_SRC)) {
/* Register CT required capabilities */
bt_pacs_cap_register(BT_AUDIO_DIR_SOURCE, &cap_source_16_1_1);
bt_pacs_cap_register(BT_AUDIO_DIR_SOURCE, &cap_source_32_1_1);
bt_pacs_cap_register(BT_AUDIO_DIR_SOURCE, &cap_source_32_2_1);
if (IS_ENABLED(CONFIG_TMAP_PERIPHERAL_LEFT)) {
bt_pacs_set_location(BT_AUDIO_DIR_SOURCE, BT_AUDIO_LOCATION_FRONT_LEFT);
} else {
bt_pacs_set_location(BT_AUDIO_DIR_SOURCE, BT_AUDIO_LOCATION_FRONT_RIGHT);
}
bt_pacs_set_supported_contexts(BT_AUDIO_DIR_SOURCE,
AVAILABLE_SOURCE_CONTEXT);
bt_pacs_set_available_contexts(BT_AUDIO_DIR_SOURCE,
AVAILABLE_SOURCE_CONTEXT);
}
for (size_t i = 0; i < ARRAY_SIZE(streams); i++) {
bt_bap_stream_cb_register(&streams[i], &stream_ops);
}
return 0;
}

View file

@ -0,0 +1,148 @@
/** @file
* @brief Bluetooth Call Control Profile (CCP) Call Controller role.
*
* Copyright (c) 2020 Nordic Semiconductor ASA
* Copyright (c) 2022 Codecoup
* Copyright 2023 NXP
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/kernel.h>
#include <zephyr/sys/printk.h>
#include <zephyr/sys/util.h>
#include <string.h>
#include <zephyr/bluetooth/conn.h>
#include <zephyr/bluetooth/audio/tbs.h>
#define URI_SEPARATOR ":"
#define CALLER_ID "friend"
static uint8_t new_call_index;
static char *remote_uri;
static K_SEM_DEFINE(sem_discovery_done, 0, 1);
static struct bt_conn *default_conn;
static void discover_cb(struct bt_conn *conn, int err, uint8_t tbs_count, bool gtbs_found)
{
if (!gtbs_found) {
printk("CCP: Failed to discover GTBS\n");
return;
}
printk("CCP: Discovered GTBS\n");
if (err) {
printk("%s (err %d)\n", __func__, err);
return;
}
/* Read Bearer URI Schemes Supported List Characteristic */
bt_tbs_client_read_uri_list(conn, BT_TBS_GTBS_INDEX);
}
static void originate_call_cb(struct bt_conn *conn, int err, uint8_t inst_index, uint8_t call_index)
{
if (inst_index != BT_TBS_GTBS_INDEX) {
printk("Unexpected %s for instance %u\n", __func__, inst_index);
return;
}
if (err) {
printk("%s (err %d)\n", __func__, err);
return;
}
printk("CCP: Call originate successful\n");
new_call_index = call_index;
}
static void terminate_call_cb(struct bt_conn *conn, int err,
uint8_t inst_index, uint8_t call_index)
{
if (inst_index != BT_TBS_GTBS_INDEX) {
printk("Unexpected %s for instance %u\n", __func__, inst_index);
return;
}
if (err) {
printk("%s (err %d)\n", __func__, err);
return;
}
printk("CCP: Call with id %d terminated\n", call_index);
}
static void read_uri_schemes_string_cb(struct bt_conn *conn, int err,
uint8_t inst_index, const char *value)
{
char *str_aux;
if (inst_index != BT_TBS_GTBS_INDEX) {
printk("Unexpected %s for instance %u\n", __func__, inst_index);
return;
}
if (err) {
printk("%s (err %d)\n", __func__, err);
return;
}
/* Save first remote URI */
str_aux = (char *)value;
remote_uri = strtok(str_aux, ",");
printk("CCP: Discovered remote URI: %s\n", remote_uri);
k_sem_give(&sem_discovery_done);
}
struct bt_tbs_client_cb tbs_client_cb = {
.discover = discover_cb,
.uri_list = read_uri_schemes_string_cb,
.originate_call = originate_call_cb,
.terminate_call = terminate_call_cb,
};
int ccp_call_ctrl_init(struct bt_conn *conn)
{
int err;
default_conn = bt_conn_ref(conn);
bt_tbs_client_register_cb(&tbs_client_cb);
err = bt_tbs_client_discover(conn, true);
if (err != 0) {
return err;
}
k_sem_take(&sem_discovery_done, K_FOREVER);
return err;
}
int ccp_originate_call(void)
{
int err;
char uri[CONFIG_BT_TBS_MAX_URI_LENGTH];
strcpy(uri, remote_uri);
strcat(uri, URI_SEPARATOR);
strcat(uri, CALLER_ID);
err = bt_tbs_client_originate_call(default_conn, BT_TBS_GTBS_INDEX, uri);
if (err != BT_TBS_RESULT_CODE_SUCCESS) {
printk("TBS originate call failed: %d\n", err);
}
return err;
}
int ccp_terminate_call(void)
{
int err;
err = bt_tbs_client_terminate_call(default_conn, BT_TBS_GTBS_INDEX, new_call_index);
if (err != BT_TBS_RESULT_CODE_SUCCESS) {
printk("TBS terminate call failed: %d\n", err);
}
return err;
}

View file

@ -0,0 +1,56 @@
/** @file
* @brief Bluetooth Coordinated Set Identifier Profile (CSIP) Set Member role.
*
* Copyright (c) 2022 Codecoup
* Copyright 2023 NXP
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/kernel.h>
#include <zephyr/sys/printk.h>
#include <zephyr/bluetooth/audio/cap.h>
#include <zephyr/bluetooth/audio/csip.h>
static struct bt_csip_set_member_svc_inst *svc_inst;
static void csip_lock_changed_cb(struct bt_conn *conn,
struct bt_csip_set_member_svc_inst *svc_inst,
bool locked)
{
printk("Client %p %s the lock\n", conn, locked ? "locked" : "released");
}
static struct bt_csip_set_member_cb csip_cb = {
.lock_changed = csip_lock_changed_cb,
};
int csip_set_member_init(void)
{
struct bt_csip_set_member_register_param param = {
.set_size = 2,
.rank = CONFIG_TMAP_PERIPHERAL_SET_RANK,
.lockable = false,
.cb = &csip_cb,
};
return bt_cap_acceptor_register(&param, &svc_inst);
}
int csip_generate_rsi(uint8_t *rsi)
{
int err;
if (svc_inst == NULL) {
return -ENODEV;
}
err = bt_csip_set_member_generate_rsi(svc_inst, rsi);
if (err) {
printk("Failed to generate RSI (err %d)\n", err);
return err;
}
return 0;
}

View file

@ -0,0 +1,308 @@
/*
* Copyright 2023 NXP
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/types.h>
#include <stddef.h>
#include <errno.h>
#include <zephyr/kernel.h>
#include <zephyr/sys/printk.h>
#include <zephyr/sys/byteorder.h>
#include <zephyr/bluetooth/bluetooth.h>
#include <zephyr/bluetooth/conn.h>
#include <zephyr/bluetooth/audio/audio.h>
#include <zephyr/bluetooth/audio/bap.h>
#include <zephyr/bluetooth/audio/bap_lc3_preset.h>
#include <zephyr/bluetooth/audio/csip.h>
#include <zephyr/bluetooth/audio/tmap.h>
#include <zephyr/bluetooth/audio/cap.h>
#include <zephyr/bluetooth/audio/mcs.h>
#include "tmap_peripheral.h"
#define AVAILABLE_SINK_CONTEXT (BT_AUDIO_CONTEXT_TYPE_UNSPECIFIED | \
BT_AUDIO_CONTEXT_TYPE_CONVERSATIONAL | \
BT_AUDIO_CONTEXT_TYPE_MEDIA | \
BT_AUDIO_CONTEXT_TYPE_GAME | \
BT_AUDIO_CONTEXT_TYPE_INSTRUCTIONAL)
static struct bt_conn *default_conn;
static struct k_work_delayable call_terminate_set_work;
static struct k_work_delayable media_pause_set_work;
static uint8_t unicast_server_addata[] = {
BT_UUID_16_ENCODE(BT_UUID_ASCS_VAL), /* ASCS UUID */
BT_AUDIO_UNICAST_ANNOUNCEMENT_TARGETED, /* Target Announcement */
(((AVAILABLE_SINK_CONTEXT) >> 0) & 0xFF),
(((AVAILABLE_SINK_CONTEXT) >> 8) & 0xFF),
0x00, /* Metadata length */
};
static uint8_t tmap_addata[] = {
BT_UUID_16_ENCODE(BT_UUID_TMAS_VAL), /* TMAS UUID */
(BT_TMAP_ROLE_UMR | BT_TMAP_ROLE_CT), 0x00, /* TMAP Role */
};
static uint8_t csis_rsi_addata[BT_CSIP_RSI_SIZE];
static bool peer_is_cg;
static bool peer_is_ums;
/* TODO: Expand with BAP data */
static const struct bt_data ad[] = {
BT_DATA_BYTES(BT_DATA_FLAGS, (BT_LE_AD_GENERAL | BT_LE_AD_NO_BREDR)),
BT_DATA_BYTES(BT_DATA_GAP_APPEARANCE, 0x09, 0x41), /* Appearance - Earbud */
BT_DATA_BYTES(BT_DATA_UUID16_SOME, BT_UUID_16_ENCODE(BT_UUID_ASCS_VAL)),
#if defined(CONFIG_BT_CSIP_SET_MEMBER)
BT_DATA(BT_DATA_CSIS_RSI, csis_rsi_addata, ARRAY_SIZE(csis_rsi_addata)),
#endif /* CONFIG_BT_CSIP_SET_MEMBER */
BT_DATA(BT_DATA_SVC_DATA16, tmap_addata, ARRAY_SIZE(tmap_addata)),
BT_DATA(BT_DATA_SVC_DATA16, unicast_server_addata, ARRAY_SIZE(unicast_server_addata)),
};
static K_SEM_DEFINE(sem_connected, 0, 1);
static K_SEM_DEFINE(sem_security_updated, 0, 1);
static K_SEM_DEFINE(sem_disconnected, 0, 1);
static K_SEM_DEFINE(sem_discovery_done, 0, 1);
void tmap_discovery_complete(enum bt_tmap_role peer_role, struct bt_conn *conn, int err)
{
if (conn != default_conn) {
return;
}
if (err) {
printk("TMAS discovery failed! (err %d)\n", err);
return;
}
peer_is_cg = (peer_role & BT_TMAP_ROLE_CG) != 0;
peer_is_ums = (peer_role & BT_TMAP_ROLE_UMS) != 0;
printk("TMAP discovery done\n");
k_sem_give(&sem_discovery_done);
}
static struct bt_tmap_cb tmap_callbacks = {
.discovery_complete = tmap_discovery_complete
};
static void connected(struct bt_conn *conn, uint8_t err)
{
char addr[BT_ADDR_LE_STR_LEN];
bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr));
if (err != 0) {
printk("Failed to connect to %s (%u)\n", addr, err);
default_conn = NULL;
return;
}
printk("Connected: %s\n", addr);
default_conn = bt_conn_ref(conn);
k_sem_give(&sem_connected);
}
static void disconnected(struct bt_conn *conn, uint8_t reason)
{
char addr[BT_ADDR_LE_STR_LEN];
if (conn != default_conn) {
return;
}
bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr));
printk("Disconnected: %s (reason 0x%02x)\n", addr, reason);
bt_conn_unref(default_conn);
default_conn = NULL;
k_sem_give(&sem_disconnected);
}
static void security_changed(struct bt_conn *conn, bt_security_t level,
enum bt_security_err err)
{
if (err == 0) {
printk("Security changed: %u\n", err);
k_sem_give(&sem_security_updated);
} else {
printk("Failed to set security level: %u", err);
}
}
BT_CONN_CB_DEFINE(conn_callbacks) = {
.connected = connected,
.disconnected = disconnected,
.security_changed = security_changed,
};
#if defined(CONFIG_BT_PRIVACY) && defined(CONFIG_BT_CSIP_SET_MEMBER)
static bool adv_rpa_expired_cb(struct bt_le_ext_adv *adv)
{
char rsi_str[13];
int err;
err = csip_generate_rsi(csis_rsi_addata);
if (err != 0) {
printk("Failed to generate RSI (err %d)\n", err);
return false;
}
snprintk(rsi_str, ARRAY_SIZE(rsi_str), "%02x%02x%02x%02x%02x%02x",
csis_rsi_addata[0], csis_rsi_addata[1], csis_rsi_addata[2],
csis_rsi_addata[3], csis_rsi_addata[4], csis_rsi_addata[5]);
printk("PRSI: 0x%s\n", rsi_str);
err = bt_le_ext_adv_set_data(adv, ad, ARRAY_SIZE(ad), NULL, 0);
if (err) {
printk("Failed to set advertising data (err %d)\n", err);
return false;
}
return true;
}
#endif /* CONFIG_BT_PRIVACY && CONFIG_BT_CSIP_SET_MEMBER */
static const struct bt_le_ext_adv_cb adv_cb = {
#if defined(CONFIG_BT_PRIVACY) && defined(CONFIG_BT_CSIP_SET_MEMBER)
.rpa_expired = adv_rpa_expired_cb,
#endif /* CONFIG_BT_PRIVACY && CONFIG_BT_CSIP_SET_MEMBER */
};
static void audio_timer_timeout(struct k_work *work)
{
int err = ccp_terminate_call();
if (err != 0) {
printk("Error sending call terminate command!\n");
}
}
static void media_play_timeout(struct k_work *work)
{
int err = mcp_send_cmd(BT_MCS_OPC_PAUSE);
if (err != 0) {
printk("Error sending pause command!\n");
}
}
void main(void)
{
int err;
struct bt_le_ext_adv *adv;
err = bt_enable(NULL);
if (err != 0) {
printk("Bluetooth init failed (err %d)\n", err);
return;
}
printk("Bluetooth initialized\n");
k_work_init_delayable(&call_terminate_set_work, audio_timer_timeout);
k_work_init_delayable(&media_pause_set_work, media_play_timeout);
printk("Initializing TMAP and setting role\n");
err = bt_tmap_register(BT_TMAP_ROLE_CT | BT_TMAP_ROLE_UMR);
if (err != 0) {
return;
}
if (IS_ENABLED(CONFIG_TMAP_PERIPHERAL_DUO)) {
err = csip_set_member_init();
if (err != 0) {
printk("CSIP Set Member init failed (err %d)\n", err);
return;
}
err = csip_generate_rsi(csis_rsi_addata);
if (err != 0) {
printk("Failed to generate RSI (err %d)\n", err);
return;
}
}
err = vcp_vol_renderer_init();
if (err != 0) {
return;
}
printk("VCP initialized\n");
err = bap_unicast_sr_init();
if (err != 0) {
return;
}
printk("BAP initialized\n");
err = bt_le_ext_adv_create(BT_LE_EXT_ADV_CONN_NAME, &adv_cb, &adv);
if (err) {
printk("Failed to create advertising set (err %d)\n", err);
return;
}
err = bt_le_ext_adv_set_data(adv, ad, ARRAY_SIZE(ad), NULL, 0);
if (err) {
printk("Failed to set advertising data (err %d)\n", err);
return;
}
err = bt_le_ext_adv_start(adv, BT_LE_EXT_ADV_START_DEFAULT);
if (err) {
printk("Failed to start advertising set (err %d)\n", err);
return;
}
printk("Advertising successfully started\n");
k_sem_take(&sem_connected, K_FOREVER);
k_sem_take(&sem_security_updated, K_FOREVER);
err = bt_tmap_discover(default_conn, &tmap_callbacks);
if (err != 0) {
return;
}
k_sem_take(&sem_discovery_done, K_FOREVER);
err = ccp_call_ctrl_init(default_conn);
if (err != 0) {
return;
}
printk("CCP initialized\n");
err = mcp_ctlr_init(default_conn);
if (err != 0) {
return;
}
printk("MCP initialized\n");
if (peer_is_cg) {
/* Initiate a call with CCP */
err = ccp_originate_call();
if (err != 0) {
printk("Error sending call originate command!\n");
}
/* Start timer to send terminate call command */
k_work_schedule(&call_terminate_set_work, K_MSEC(2000));
}
if (peer_is_ums) {
/* Play media with MCP */
err = mcp_send_cmd(BT_MCS_OPC_PLAY);
if (err != 0)
printk("Error sending media play command!\n");
/* Start timer to send media pause command */
k_work_schedule(&media_pause_set_work, K_MSEC(2000));
err = k_sem_take(&sem_disconnected, K_FOREVER);
if (err != 0) {
printk("failed to take sem_disconnected (err %d)\n", err);
}
}
}

View file

@ -0,0 +1,83 @@
/** @file
* @brief Bluetooth Media Control Profile (MCP) Controller role.
*
* Copyright 2023 NXP
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/kernel.h>
#include <zephyr/sys/printk.h>
#include <zephyr/bluetooth/conn.h>
#include <zephyr/bluetooth/audio/mcc.h>
#include <zephyr/bluetooth/audio/media_proxy.h>
static struct bt_conn *default_conn;
static K_SEM_DEFINE(sem_discovery_done, 0, 1);
static void mcc_discover_mcs_cb(struct bt_conn *conn, int err)
{
if (err) {
printk("MCP: Discovery of MCS failed (%d)\n", err);
} else {
printk("MCP: Discovered MCS\n");
}
k_sem_give(&sem_discovery_done);
}
static void mcc_send_command_cb(struct bt_conn *conn, int err, const struct mpl_cmd *cmd)
{
if (err) {
printk("MCP: Command send failed (%d) - opcode: %u, param: %d\n",
err, cmd->opcode, cmd->param);
} else {
printk("MCP: Successfully sent command (%d) - opcode: %u, param: %d\n",
err, cmd->opcode, cmd->param);
}
}
static struct bt_mcc_cb mcc_cb = {
.discover_mcs = mcc_discover_mcs_cb,
.send_cmd = mcc_send_command_cb,
};
int mcp_ctlr_init(struct bt_conn *conn)
{
int err;
default_conn = bt_conn_ref(conn);
err = bt_mcc_init(&mcc_cb);
if (err != 0) {
return err;
}
err = bt_mcc_discover_mcs(default_conn, true);
if (err == 0) {
k_sem_take(&sem_discovery_done, K_FOREVER);
}
return err;
}
int mcp_send_cmd(uint8_t mcp_opcode)
{
int err;
struct mpl_cmd cmd;
cmd.opcode = mcp_opcode;
cmd.use_param = false;
if (default_conn == NULL) {
printk("MCP: No connection\n");
return -EINVAL;
}
err = bt_mcc_send_cmd(default_conn, &cmd);
if (err != 0) {
printk("MCP: Command failed: %d\n", err);
}
return err;
}

View file

@ -0,0 +1,78 @@
/*
* Copyright 2023 NXP
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/types.h>
/**
* @brief Initialize the VCP Volume Renderer role
*
* @return 0 if success, errno on failure.
*/
int vcp_vol_renderer_init(void);
/**
* @brief Initialize the CSIP Set Member role
*
* @return 0 if success, errno on failure.
*/
int csip_set_member_init(void);
/**
* @brief Generate the Resolvable Set Identifier (RSI) value.
*
* @param rsi Pointer to place the 6-octet newly generated RSI data.
*
* @return 0 if on success, errno on error.
*/
int csip_generate_rsi(uint8_t *rsi);
/**
* @brief Initialize BAP Unicast Server role
*
* @return 0 if success, errno on failure.
*/
int bap_unicast_sr_init(void);
/**
* @brief Initialize Call Control Client
*
* @param conn Pointer to connection.
*
* @return 0 if success, errno on failure.
*/
int ccp_call_ctrl_init(struct bt_conn *conn);
/**
* @brief Initiate a originate call command
*
* @return 0 if success, errno on failure.
*/
int ccp_originate_call(void);
/**
* @brief Initiate a terminate call command
*
* @return 0 if success, errno on failure.
*/
int ccp_terminate_call(void);
/**
* @brief Initialize Media Controller
*
* @param conn Pointer to connection.
*
* @return 0 if success, errno on failure.
*/
int mcp_ctlr_init(struct bt_conn *conn);
/**
* @brief Send a command to the Media Player
*
* @param mcp_opcode Command opcode.
*
* @return 0 if on success, errno on error.
*/
int mcp_send_cmd(uint8_t mcp_opcode);

View file

@ -0,0 +1,68 @@
/** @file
* @brief Bluetooth Volume Control Profile (VCP) Volume Renderer role.
*
* Copyright (c) 2020 Bose Corporation
* Copyright (c) 2020-2022 Nordic Semiconductor ASA
* Copyright (c) 2022 Codecoup
* Copyright 2023 NXP
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/kernel.h>
#include <zephyr/sys/printk.h>
#include <stdlib.h>
#include <stdio.h>
#include <zephyr/bluetooth/conn.h>
#include <zephyr/bluetooth/audio/vcp.h>
static struct bt_vcp_included vcp_included;
static void vcs_state_cb(int err, uint8_t volume, uint8_t mute)
{
if (err) {
printk("VCS state get failed (%d)\n", err);
} else {
printk("VCS volume %u, mute %u\n", volume, mute);
}
}
static void vcs_flags_cb(int err, uint8_t flags)
{
if (err) {
printk("VCS flags get failed (%d)\n", err);
} else {
printk("VCS flags 0x%02X\n", flags);
}
}
static struct bt_vcp_vol_rend_cb vcp_cbs = {
.state = vcs_state_cb,
.flags = vcs_flags_cb,
};
int vcp_vol_renderer_init(void)
{
int err;
struct bt_vcp_vol_rend_register_param vcp_register_param;
memset(&vcp_register_param, 0, sizeof(vcp_register_param));
vcp_register_param.step = 1;
vcp_register_param.mute = BT_VCP_STATE_UNMUTED;
vcp_register_param.volume = 100;
vcp_register_param.cb = &vcp_cbs;
err = bt_vcp_vol_rend_register(&vcp_register_param);
if (err) {
return err;
}
err = bt_vcp_vol_rend_included_get(&vcp_included);
if (err != 0) {
return err;
}
return 0;
}

View file

@ -60,3 +60,4 @@ zephyr_library_sources_ifdef(CONFIG_BT_HAS_CLIENT has_client.c)
zephyr_library_sources_ifdef(CONFIG_BT_CAP cap_stream.c)
zephyr_library_sources_ifdef(CONFIG_BT_CAP_ACCEPTOR cap_acceptor.c)
zephyr_library_sources_ifdef(CONFIG_BT_CAP_INITIATOR cap_initiator.c)
zephyr_library_sources_ifdef(CONFIG_BT_TMAP tmap.c)

View file

@ -50,6 +50,7 @@ rsource "Kconfig.has"
rsource "Kconfig.mpl"
rsource "Kconfig.mctl"
rsource "Kconfig.cap"
rsource "Kconfig.tmap"
module = BT_AUDIO
module-str = "Bluetooth Audio"

View file

@ -0,0 +1,26 @@
# Bluetooth Audio - Telephony and Media Audio Profile (TMAP) options
#
# Copyright 2023 NXP
#
# SPDX-License-Identifier: Apache-2.0
#
config BT_TMAP
bool "Telephony and Media Audio Profile [EXPERIMENTAL]"
depends on BT_CAP_ACCEPTOR || BT_CAP_INITIATOR
select EXPERIMENTAL
help
Enabling this will enable TMAP.
config BT_DEBUG_TMAP
bool "Telephony and Media Audio Profile debug"
select DEPRECATED
depends on BT_TMAP
help
Use this option to enable Telephony and Media Audio Profile debug
logs for the Bluetooth Audio functionality.
module = BT_TMAP
legacy-debug-sym = BT_DEBUG_TMAP
module-str = "Telephony and Media Audio Profile"
source "subsys/bluetooth/common/Kconfig.template.log_config_bt"

View file

@ -0,0 +1,192 @@
/*
* Copyright 2023 NXP
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/kernel.h>
#include <zephyr/sys/byteorder.h>
#include <zephyr/types.h>
#include <zephyr/device.h>
#include <zephyr/init.h>
#include <stdlib.h>
#include <zephyr/bluetooth/bluetooth.h>
#include <zephyr/bluetooth/conn.h>
#include <zephyr/bluetooth/gatt.h>
#include "audio_internal.h"
#include <zephyr/bluetooth/audio/tmap.h>
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(bt_tmap, CONFIG_BT_TMAP_LOG_LEVEL);
/* Hex value if all TMAP role bits are set */
#define TMAP_ALL_ROLES 0x3F
static uint16_t tmap_role;
static struct bt_tmap_cb *cb;
static bool tmas_found;
static struct bt_uuid_16 uuid[CONFIG_BT_MAX_CONN] = {BT_UUID_INIT_16(0)};
static struct bt_gatt_discover_params discover_params[CONFIG_BT_MAX_CONN];
static struct bt_gatt_read_params read_params[CONFIG_BT_MAX_CONN];
uint8_t tmap_char_read(struct bt_conn *conn, uint8_t err,
struct bt_gatt_read_params *params,
const void *data, uint16_t length)
{
uint16_t peer_role;
/* Check read request result */
if (err != BT_ATT_ERR_SUCCESS) {
if (cb->discovery_complete) {
cb->discovery_complete(0, conn, err);
}
return BT_GATT_ITER_STOP;
}
/* Check data length */
if (length != sizeof(peer_role)) {
if (cb->discovery_complete) {
cb->discovery_complete(0, conn, BT_ATT_ERR_INVALID_ATTRIBUTE_LEN);
}
return BT_GATT_ITER_STOP;
}
/* Extract the TMAP role of the peer and inform application fo the value found */
peer_role = sys_get_le16(data);
if ((peer_role > 0U) && (peer_role <= TMAP_ALL_ROLES)) {
if (cb->discovery_complete) {
cb->discovery_complete((enum bt_tmap_role)peer_role, conn, 0);
}
} else {
if (cb->discovery_complete) {
cb->discovery_complete(0, conn, BT_ATT_ERR_VALUE_NOT_ALLOWED);
}
}
return BT_GATT_ITER_STOP;
}
static uint8_t discover_func(struct bt_conn *conn, const struct bt_gatt_attr *attr,
struct bt_gatt_discover_params *params)
{
int err;
uint8_t conn_id = bt_conn_index(conn);
if (!attr) {
(void)memset(params, 0, sizeof(*params));
if (!tmas_found) {
/* TMAS not found on peer */
if (cb->discovery_complete) {
cb->discovery_complete(0, conn, BT_ATT_ERR_ATTRIBUTE_NOT_FOUND);
}
}
tmas_found = false;
return BT_GATT_ITER_STOP;
}
if (!bt_uuid_cmp(discover_params[conn_id].uuid, BT_UUID_TMAS)) {
LOG_DBG("Discovered TMAS\n");
tmas_found = true;
memcpy(&uuid[conn_id], BT_UUID_GATT_TMAPR, sizeof(uuid));
discover_params[conn_id].uuid = &uuid[conn_id].uuid;
discover_params[conn_id].start_handle = attr->handle + 1;
discover_params[conn_id].type = BT_GATT_DISCOVER_CHARACTERISTIC;
/* Discovered TMAS - Search for TMAP Role characteristic */
err = bt_gatt_discover(conn, &discover_params[conn_id]);
if (err) {
LOG_DBG("Discover failed (err %d)\n", err);
}
} else if (!bt_uuid_cmp(discover_params[conn_id].uuid, BT_UUID_GATT_TMAPR)) {
/* Use 0 for now, will expand later */
read_params[conn_id].func = tmap_char_read;
read_params[conn_id].handle_count = 1u;
read_params[conn_id].single.handle = bt_gatt_attr_value_handle(attr);
read_params[conn_id].single.offset = 0;
/* Discovered TMAP Role characteristic - read value */
err = bt_gatt_read(conn, &read_params[0]);
if (err != 0) {
printk("Could not read peer TMAP Role\n");
}
} else {
return BT_GATT_ITER_CONTINUE;
}
return BT_GATT_ITER_STOP;
}
static ssize_t read_role(struct bt_conn *conn,
const struct bt_gatt_attr *attr, void *buf,
uint16_t len, uint16_t offset)
{
uint16_t role;
role = sys_cpu_to_le16(tmap_role);
LOG_DBG("TMAP: role 0x%04X", role);
return bt_gatt_attr_read(conn, attr, buf, len, offset,
&role, sizeof(role));
}
/* Telephony and Media Audio Service attributes */
#define BT_TMAS_SERVICE_DEFINITION \
BT_GATT_PRIMARY_SERVICE(BT_UUID_TMAS), \
BT_AUDIO_CHRC(BT_UUID_GATT_TMAPR, \
BT_GATT_CHRC_READ, \
BT_GATT_PERM_READ_ENCRYPT, \
read_role, NULL, NULL)
static struct bt_gatt_attr svc_attrs[] = { BT_TMAS_SERVICE_DEFINITION };
static struct bt_gatt_service tmas;
int bt_tmap_register(enum bt_tmap_role role)
{
int err;
tmas = (struct bt_gatt_service)BT_GATT_SERVICE(svc_attrs);
err = bt_gatt_service_register(&tmas);
if (err) {
LOG_DBG("Could not register the TMAS service");
return -ENOEXEC;
}
tmap_role = role;
tmas_found = false;
return 0;
}
int bt_tmap_discover(struct bt_conn *conn, struct bt_tmap_cb *tmap_cb)
{
int err = 0;
uint8_t conn_id = bt_conn_index(conn);
cb = tmap_cb;
memcpy(&uuid[conn_id], BT_UUID_TMAS, sizeof(uuid));
discover_params[conn_id].uuid = &uuid[conn_id].uuid;
discover_params[conn_id].func = discover_func;
discover_params[conn_id].start_handle = BT_ATT_FIRST_ATTRIBUTE_HANDLE;
discover_params[conn_id].end_handle = BT_ATT_LAST_ATTRIBUTE_HANDLE;
discover_params[conn_id].type = BT_GATT_DISCOVER_PRIMARY;
err = bt_gatt_discover(conn, &discover_params[conn_id]);
return err;
}
void bt_tmap_set_role(enum bt_tmap_role role)
{
tmap_role = role;
}

View file

@ -119,6 +119,9 @@ CONFIG_BT_CAP_ACCEPTOR=y
CONFIG_BT_CAP_ACCEPTOR_SET_MEMBER=y
CONFIG_BT_CAP_INITIATOR=y
# Telephony and Media Audio Profile
CONFIG_BT_TMAP=y
# DEBUGGING
CONFIG_BT_DEBUG_LOG=y
CONFIG_BT_VCP_VOL_REND_LOG_LEVEL_DBG=y

View file

@ -30,6 +30,8 @@ extern struct bst_test_list *test_has_install(struct bst_test_list *tests);
extern struct bst_test_list *test_has_client_install(struct bst_test_list *tests);
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_tmap_client_install(struct bst_test_list *tests);
extern struct bst_test_list *test_tmap_server_install(struct bst_test_list *tests);
bst_test_install_t test_installers[] = {
test_vcp_install,
@ -56,6 +58,8 @@ bst_test_install_t test_installers[] = {
test_has_client_install,
test_ias_install,
test_ias_client_install,
test_tmap_server_install,
test_tmap_client_install,
NULL
};

View file

@ -0,0 +1,176 @@
/*
* Copyright 2023 NXP
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifdef CONFIG_BT_TMAP
#include <stdint.h>
#include <stddef.h>
#include <errno.h>
#include <zephyr/types.h>
#include <zephyr/sys/byteorder.h>
#include <zephyr/bluetooth/bluetooth.h>
#include <zephyr/bluetooth/conn.h>
#include <zephyr/bluetooth/uuid.h>
#include <zephyr/bluetooth/gatt.h>
#include <zephyr/bluetooth/audio/tmap.h>
#include "common.h"
extern enum bst_result_t bst_result;
CREATE_FLAG(flag_tmap_discovered);
void tmap_discovery_complete_cb(enum bt_tmap_role role, struct bt_conn *conn, int err)
{
printk("TMAS discovery done\n");
SET_FLAG(flag_tmap_discovered);
}
static struct bt_tmap_cb tmap_callbacks = {
.discovery_complete = tmap_discovery_complete_cb
};
static bool check_audio_support_and_connect(struct bt_data *data, void *user_data)
{
bt_addr_le_t *addr = user_data;
struct net_buf_simple tmas_svc_data;
struct bt_uuid *uuid;
uint16_t uuid_val;
uint16_t peer_tmap_role = 0;
int err;
printk("[AD]: %u data_len %u\n", data->type, data->data_len);
if (data->type != BT_DATA_SVC_DATA16) {
return true; /* Continue parsing to next AD data type */
}
if (data->data_len < sizeof(uuid_val)) {
printk("AD invalid size %u\n", data->data_len);
return true; /* Continue parsing to next AD data type */
}
net_buf_simple_init_with_data(&tmas_svc_data, (void *)data->data, data->data_len);
uuid_val = net_buf_simple_pull_le16(&tmas_svc_data);
uuid = BT_UUID_DECLARE_16(sys_le16_to_cpu(uuid_val));
if (bt_uuid_cmp(uuid, BT_UUID_TMAS) != 0) {
/* We are looking for the TMAS service data */
return true; /* Continue parsing to next AD data type */
}
printk("Found TMAS in peer adv data!\n");
if (tmas_svc_data.len < sizeof(peer_tmap_role)) {
printk("AD invalid size %u\n", data->data_len);
return false; /* Stop parsing */
}
peer_tmap_role = net_buf_simple_pull_le16(&tmas_svc_data);
if (!(peer_tmap_role & BT_TMAP_ROLE_UMR)) {
printk("No TMAS UMR support!\n");
return false; /* Stop parsing */
}
printk("Attempt to connect!\n");
err = bt_le_scan_stop();
if (err != 0) {
printk("Failed to stop scan: %d\n", err);
return false;
}
err = bt_conn_le_create(addr, BT_CONN_LE_CREATE_CONN,
BT_LE_CONN_PARAM_DEFAULT,
&default_conn);
if (err != 0) {
printk("Create conn to failed (%u)\n", err);
bt_le_scan_start(BT_LE_SCAN_PASSIVE, NULL);
}
return false; /* Stop parsing */
}
static void scan_recv(const struct bt_le_scan_recv_info *info,
struct net_buf_simple *buf)
{
char le_addr[BT_ADDR_LE_STR_LEN];
printk("SCAN RCV CB\n");
/* Check for connectable, extended advertising */
if (((info->adv_props & BT_GAP_ADV_PROP_EXT_ADV) != 0) ||
((info->adv_props & BT_GAP_ADV_PROP_CONNECTABLE)) != 0) {
bt_addr_le_to_str(info->addr, le_addr, sizeof(le_addr));
printk("[DEVICE]: %s, ", le_addr);
/* Check for TMAS support in advertising data */
bt_data_parse(buf, check_audio_support_and_connect, (void *)info->addr);
}
}
static struct bt_le_scan_cb scan_callbacks = {
.recv = scan_recv,
};
static void test_main(void)
{
int err;
err = bt_enable(NULL);
if (err != 0) {
FAIL("Bluetooth init failed (err %d)\n", err);
return;
}
printk("Bluetooth initialized\n");
/* Initialize TMAP */
err = bt_tmap_register(BT_TMAP_ROLE_CG | BT_TMAP_ROLE_UMS);
if (err != 0) {
return;
}
printk("TMAP initialized. Start scanning...\n");
/* Scan for peer */
bt_le_scan_cb_register(&scan_callbacks);
err = bt_le_scan_start(BT_LE_SCAN_PASSIVE, NULL);
if (err != 0) {
FAIL("Scanning failed to start (err %d)\n", err);
return;
}
printk("Scanning successfully started\n");
WAIT_FOR_FLAG(flag_connected);
/* Discover TMAS service on peer */
err = bt_tmap_discover(default_conn, &tmap_callbacks);
if (err != 0) {
FAIL("Failed to initiate TMAS discovery: %d\n", err);
return;
}
printk("TMAP Central Starting Service Discovery...\n");
WAIT_FOR_FLAG(flag_tmap_discovered);
PASS("TMAP Client test passed\n");
}
static const struct bst_test_instance test_tmap_client[] = {
{
.test_id = "tmap_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_tmap_client_install(struct bst_test_list *tests)
{
return bst_add_tests(tests, test_tmap_client);
}
#else
struct bst_test_list *test_tmap_client_install(struct bst_test_list *tests)
{
return tests;
}
#endif /* CONFIG_BT_TMAP */

View file

@ -0,0 +1,101 @@
/*
* Copyright 2023 NXP
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifdef CONFIG_BT_TMAP
#include <stdint.h>
#include <stddef.h>
#include <errno.h>
#include <zephyr/types.h>
#include <zephyr/bluetooth/bluetooth.h>
#include <zephyr/bluetooth/hci.h>
#include <zephyr/bluetooth/conn.h>
#include <zephyr/bluetooth/uuid.h>
#include <zephyr/bluetooth/gatt.h>
#include <zephyr/bluetooth/audio/tmap.h>
#include "common.h"
extern enum bst_result_t bst_result;
static uint8_t tmap_addata[] = {
BT_UUID_16_ENCODE(BT_UUID_TMAS_VAL), /* TMAS UUID */
(BT_TMAP_ROLE_UMR | BT_TMAP_ROLE_CT), 0x00, /* TMAP Role */
};
static const struct bt_data ad_tmas[] = {
BT_DATA_BYTES(BT_DATA_FLAGS, (BT_LE_AD_GENERAL | BT_LE_AD_NO_BREDR)),
BT_DATA_BYTES(BT_DATA_GAP_APPEARANCE, 0x09, 0x41), /* Appearance - Earbud */
BT_DATA(BT_DATA_SVC_DATA16, tmap_addata, ARRAY_SIZE(tmap_addata)),
};
static void test_main(void)
{
int err;
struct bt_le_ext_adv *adv;
err = bt_enable(NULL);
if (err != 0) {
FAIL("Bluetooth init failed (err %d)\n", err);
return;
}
printk("Bluetooth initialized\n");
/* Initialize TMAP */
err = bt_tmap_register(BT_TMAP_ROLE_CT | BT_TMAP_ROLE_UMR);
if (err != 0) {
return;
}
printk("TMAP initialized. Start advertising...\n");
/* Create a connectable extended advertising set */
err = bt_le_ext_adv_create(BT_LE_EXT_ADV_CONN_NAME, NULL, &adv);
if (err) {
printk("Failed to create advertising set (err %d)\n", err);
return;
}
err = bt_le_ext_adv_set_data(adv, ad_tmas, ARRAY_SIZE(ad_tmas), NULL, 0);
if (err) {
printk("Failed to set advertising data (err %d)\n", err);
return;
}
err = bt_le_ext_adv_start(adv, BT_LE_EXT_ADV_START_DEFAULT);
if (err) {
printk("Failed to start advertising set (err %d)\n", err);
return;
}
printk("Advertising successfully started\n");
WAIT_FOR_FLAG(flag_connected);
printk("Connected!\n");
PASS("TMAP test passed\n");
}
static const struct bst_test_instance test_tmas[] = {
{
.test_id = "tmap_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_tmap_server_install(struct bst_test_list *tests)
{
return bst_add_tests(tests, test_tmas);
}
#else
struct bst_test_list *test_tmap_server_install(struct bst_test_list *tests)
{
return tests;
}
#endif /* CONFIG_BT_TMAP */

View file

@ -0,0 +1,27 @@
#!/usr/bin/env bash
#
# Copyright 2022 NXP
#
# SPDX-License-Identifier: Apache-2.0
SIMULATION_ID="tmap"
VERBOSITY_LEVEL=2
EXECUTE_TIMEOUT=30
source ${ZEPHYR_BASE}/tests/bsim/sh_common.source
cd ${BSIM_OUT_PATH}/bin
printf "\n\n======== Running TMAP client & server test =========\n\n"
Execute ./bs_${BOARD}_tests_bsim_bluetooth_audio_prj_conf \
-v=${VERBOSITY_LEVEL} -s=${SIMULATION_ID} -d=0 -testid=tmap_client -rs=24
Execute ./bs_${BOARD}_tests_bsim_bluetooth_audio_prj_conf \
-v=${VERBOSITY_LEVEL} -s=${SIMULATION_ID} -d=1 -testid=tmap_server -rs=23
# Simulation time should be larger than the WAIT_TIME in common.h
Execute ./bs_2G4_phy_v1 -v=${VERBOSITY_LEVEL} -s=${SIMULATION_ID} \
-D=2 -sim_length=60e6 $@
wait_for_background_jobs