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:
parent
e05df8faf1
commit
5096aa1c00
41 changed files with 3287 additions and 0 deletions
66
include/zephyr/bluetooth/audio/tmap.h
Normal file
66
include/zephyr/bluetooth/audio/tmap.h
Normal 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_ */
|
15
samples/bluetooth/tmap_central/CMakeLists.txt
Normal file
15
samples/bluetooth/tmap_central/CMakeLists.txt
Normal 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)
|
22
samples/bluetooth/tmap_central/README.rst
Normal file
22
samples/bluetooth/tmap_central/README.rst
Normal 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.
|
10
samples/bluetooth/tmap_central/boards/native_posix.conf
Normal file
10
samples/bluetooth/tmap_central/boards/native_posix.conf
Normal 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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
47
samples/bluetooth/tmap_central/prj.conf
Normal file
47
samples/bluetooth/tmap_central/prj.conf
Normal 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"
|
10
samples/bluetooth/tmap_central/sample.yaml
Normal file
10
samples/bluetooth/tmap_central/sample.yaml
Normal 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
|
513
samples/bluetooth/tmap_central/src/cap_initiator.c
Normal file
513
samples/bluetooth/tmap_central/src/cap_initiator.c
Normal 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, ¶ms);
|
||||||
|
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, ¶ms);
|
||||||
|
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(¶m, 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 */
|
54
samples/bluetooth/tmap_central/src/ccp_server.c
Normal file
54
samples/bluetooth/tmap_central/src/ccp_server.c
Normal 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;
|
||||||
|
}
|
334
samples/bluetooth/tmap_central/src/main.c
Normal file
334
samples/bluetooth/tmap_central/src/main.c
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
24
samples/bluetooth/tmap_central/src/mcp_server.c
Normal file
24
samples/bluetooth/tmap_central/src/mcp_server.c
Normal 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;
|
||||||
|
}
|
63
samples/bluetooth/tmap_central/src/tmap_central.h
Normal file
63
samples/bluetooth/tmap_central/src/tmap_central.h
Normal 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);
|
143
samples/bluetooth/tmap_central/src/vcp_vol_ctlr.c
Normal file
143
samples/bluetooth/tmap_central/src/vcp_vol_ctlr.c
Normal 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;
|
||||||
|
}
|
19
samples/bluetooth/tmap_peripheral/CMakeLists.txt
Normal file
19
samples/bluetooth/tmap_peripheral/CMakeLists.txt
Normal 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)
|
50
samples/bluetooth/tmap_peripheral/Kconfig
Normal file
50
samples/bluetooth/tmap_peripheral/Kconfig
Normal 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
|
22
samples/bluetooth/tmap_peripheral/README.rst
Normal file
22
samples/bluetooth/tmap_peripheral/README.rst
Normal 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.
|
10
samples/bluetooth/tmap_peripheral/boards/native_posix.conf
Normal file
10
samples/bluetooth/tmap_peripheral/boards/native_posix.conf
Normal 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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
3
samples/bluetooth/tmap_peripheral/duo.conf
Normal file
3
samples/bluetooth/tmap_peripheral/duo.conf
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
CONFIG_BT_CSIP_SET_MEMBER=y
|
||||||
|
CONFIG_TMAP_PERIPHERAL_DUO=y
|
||||||
|
CONFIG_BT_CAP_ACCEPTOR_SET_MEMBER=y
|
55
samples/bluetooth/tmap_peripheral/prj.conf
Normal file
55
samples/bluetooth/tmap_peripheral/prj.conf
Normal 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"
|
19
samples/bluetooth/tmap_peripheral/sample.yaml
Normal file
19
samples/bluetooth/tmap_peripheral/sample.yaml
Normal 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
|
496
samples/bluetooth/tmap_peripheral/src/bap_unicast_sr.c
Normal file
496
samples/bluetooth/tmap_peripheral/src/bap_unicast_sr.c
Normal 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;
|
||||||
|
}
|
148
samples/bluetooth/tmap_peripheral/src/ccp_call_ctrl.c
Normal file
148
samples/bluetooth/tmap_peripheral/src/ccp_call_ctrl.c
Normal 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;
|
||||||
|
}
|
56
samples/bluetooth/tmap_peripheral/src/csip_set_member.c
Normal file
56
samples/bluetooth/tmap_peripheral/src/csip_set_member.c
Normal 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(¶m, &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;
|
||||||
|
}
|
308
samples/bluetooth/tmap_peripheral/src/main.c
Normal file
308
samples/bluetooth/tmap_peripheral/src/main.c
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
83
samples/bluetooth/tmap_peripheral/src/mcp_ctlr.c
Normal file
83
samples/bluetooth/tmap_peripheral/src/mcp_ctlr.c
Normal 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;
|
||||||
|
}
|
78
samples/bluetooth/tmap_peripheral/src/tmap_peripheral.h
Normal file
78
samples/bluetooth/tmap_peripheral/src/tmap_peripheral.h
Normal 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);
|
68
samples/bluetooth/tmap_peripheral/src/vcp_vol_renderer.c
Normal file
68
samples/bluetooth/tmap_peripheral/src/vcp_vol_renderer.c
Normal 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;
|
||||||
|
}
|
|
@ -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 cap_stream.c)
|
||||||
zephyr_library_sources_ifdef(CONFIG_BT_CAP_ACCEPTOR cap_acceptor.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_CAP_INITIATOR cap_initiator.c)
|
||||||
|
zephyr_library_sources_ifdef(CONFIG_BT_TMAP tmap.c)
|
||||||
|
|
|
@ -50,6 +50,7 @@ rsource "Kconfig.has"
|
||||||
rsource "Kconfig.mpl"
|
rsource "Kconfig.mpl"
|
||||||
rsource "Kconfig.mctl"
|
rsource "Kconfig.mctl"
|
||||||
rsource "Kconfig.cap"
|
rsource "Kconfig.cap"
|
||||||
|
rsource "Kconfig.tmap"
|
||||||
|
|
||||||
module = BT_AUDIO
|
module = BT_AUDIO
|
||||||
module-str = "Bluetooth Audio"
|
module-str = "Bluetooth Audio"
|
||||||
|
|
26
subsys/bluetooth/audio/Kconfig.tmap
Normal file
26
subsys/bluetooth/audio/Kconfig.tmap
Normal 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"
|
192
subsys/bluetooth/audio/tmap.c
Normal file
192
subsys/bluetooth/audio/tmap.c
Normal 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;
|
||||||
|
}
|
|
@ -119,6 +119,9 @@ CONFIG_BT_CAP_ACCEPTOR=y
|
||||||
CONFIG_BT_CAP_ACCEPTOR_SET_MEMBER=y
|
CONFIG_BT_CAP_ACCEPTOR_SET_MEMBER=y
|
||||||
CONFIG_BT_CAP_INITIATOR=y
|
CONFIG_BT_CAP_INITIATOR=y
|
||||||
|
|
||||||
|
# Telephony and Media Audio Profile
|
||||||
|
CONFIG_BT_TMAP=y
|
||||||
|
|
||||||
# DEBUGGING
|
# DEBUGGING
|
||||||
CONFIG_BT_DEBUG_LOG=y
|
CONFIG_BT_DEBUG_LOG=y
|
||||||
CONFIG_BT_VCP_VOL_REND_LOG_LEVEL_DBG=y
|
CONFIG_BT_VCP_VOL_REND_LOG_LEVEL_DBG=y
|
||||||
|
|
|
@ -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_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_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_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,
|
||||||
|
@ -56,6 +58,8 @@ bst_test_install_t test_installers[] = {
|
||||||
test_has_client_install,
|
test_has_client_install,
|
||||||
test_ias_install,
|
test_ias_install,
|
||||||
test_ias_client_install,
|
test_ias_client_install,
|
||||||
|
test_tmap_server_install,
|
||||||
|
test_tmap_client_install,
|
||||||
NULL
|
NULL
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
176
tests/bsim/bluetooth/audio/src/tmap_client_test.c
Normal file
176
tests/bsim/bluetooth/audio/src/tmap_client_test.c
Normal 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 */
|
101
tests/bsim/bluetooth/audio/src/tmap_server_test.c
Normal file
101
tests/bsim/bluetooth/audio/src/tmap_server_test.c
Normal 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 */
|
27
tests/bsim/bluetooth/audio/test_scripts/tmap.sh
Normal file
27
tests/bsim/bluetooth/audio/test_scripts/tmap.sh
Normal 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
|
Loading…
Add table
Add a link
Reference in a new issue