Bluetooth: samples: Add HAP Hearing Aid sample application
This adds sample application that can be used as reference implementation and for HAP qualification purposes. Signed-off-by: Mariusz Skamra <mariusz.skamra@codecoup.pl>
This commit is contained in:
parent
cedc99b8ed
commit
acb9da2ba0
16 changed files with 1482 additions and 0 deletions
26
samples/bluetooth/hap_ha/CMakeLists.txt
Normal file
26
samples/bluetooth/hap_ha/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
cmake_minimum_required(VERSION 3.20.0)
|
||||
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
|
||||
project(hap_ha)
|
||||
|
||||
target_sources(app PRIVATE
|
||||
src/bap_unicast_sr.c
|
||||
src/has_server.c
|
||||
src/main.c
|
||||
src/vcp_vol_renderer.c
|
||||
)
|
||||
|
||||
target_sources_ifdef(CONFIG_BT_HAS_HEARING_AID_BINAURAL app PRIVATE
|
||||
src/csip_set_member.c
|
||||
)
|
||||
|
||||
target_sources_ifdef(CONFIG_BT_ASCS_ASE_SRC app PRIVATE
|
||||
src/micp_mic_dev.c
|
||||
)
|
||||
|
||||
target_sources_ifdef(CONFIG_BT_TBS_CLIENT app PRIVATE
|
||||
src/ccp_call_ctrl.c
|
||||
)
|
||||
|
||||
zephyr_library_include_directories(${ZEPHYR_BASE}/samples/bluetooth)
|
||||
22
samples/bluetooth/hap_ha/Kconfig
Normal file
22
samples/bluetooth/hap_ha/Kconfig
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
#
|
||||
# Copyright (c) 2022 Codecoup
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
|
||||
mainmenu "Bluetooth: Hearing Aid"
|
||||
|
||||
menu "Zephyr"
|
||||
source "Kconfig.zephyr"
|
||||
endmenu
|
||||
|
||||
menu "Hearing Aid"
|
||||
|
||||
config HAP_HA_SET_RANK
|
||||
int "Device rank in set"
|
||||
depends on BT_CSIS
|
||||
range 1 2
|
||||
help
|
||||
Rank of this device in set.
|
||||
|
||||
endmenu
|
||||
24
samples/bluetooth/hap_ha/README.rst
Normal file
24
samples/bluetooth/hap_ha/README.rst
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
.. _bluetooth_hap_ha:
|
||||
|
||||
Bluetooth: HAP Hearing Aid (HA)
|
||||
###############################
|
||||
|
||||
Overview
|
||||
********
|
||||
|
||||
Application demonstrating the LE Audio Hearing Aid sample functionality.
|
||||
Starts advertising and awaits connection from a Hearing Aid Unicast Client (HAUC)
|
||||
or Hearing Aid Remote Controller (HARC).
|
||||
|
||||
Requirements
|
||||
************
|
||||
|
||||
* BlueZ running on the host, or
|
||||
* A board with Bluetooth Low Energy 5.2 support
|
||||
|
||||
Building and Running
|
||||
********************
|
||||
This sample can be found under
|
||||
:zephyr_file:`samples/bluetooth/hap_ha` in the Zephyr tree.
|
||||
|
||||
See :ref:`bluetooth samples section <bluetooth-samples>` for details.
|
||||
6
samples/bluetooth/hap_ha/banded.conf
Normal file
6
samples/bluetooth/hap_ha/banded.conf
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
CONFIG_BT_HAS_HEARING_AID_BANDED=y
|
||||
|
||||
# If the HA supports the Volume Balance feature (see Section 3.1) and the HA
|
||||
# is a Banded Hearing Aid, the HA shall instantiate two instances of VOCS.
|
||||
CONFIG_BT_VOCS_MAX_INSTANCE_COUNT=2
|
||||
CONFIG_BT_VCS_VOCS_INSTANCE_COUNT=2
|
||||
3
samples/bluetooth/hap_ha/binaural.conf
Normal file
3
samples/bluetooth/hap_ha/binaural.conf
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
CONFIG_BT_CSIS=y
|
||||
CONFIG_BT_HAS_HEARING_AID_BINAURAL=y
|
||||
CONFIG_BT_CAP_ACCEPTOR_SET_MEMBER=y
|
||||
7
samples/bluetooth/hap_ha/boards/native_posix.conf
Normal file
7
samples/bluetooth/hap_ha/boards/native_posix.conf
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
CONFIG_LOG_MODE_IMMEDIATE=y
|
||||
CONFIG_BT_TINYCRYPT_ECC=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
|
||||
58
samples/bluetooth/hap_ha/prj.conf
Normal file
58
samples/bluetooth/hap_ha/prj.conf
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
CONFIG_BT=y
|
||||
CONFIG_BT_PERIPHERAL=y
|
||||
CONFIG_BT_PRIVACY=y
|
||||
|
||||
CONFIG_BT_SMP=y
|
||||
CONFIG_BT_SMP_SC_PAIR_ONLY=y
|
||||
CONFIG_BT_SMP_MIN_ENC_KEY_SIZE=16
|
||||
|
||||
CONFIG_BT_EXT_ADV=y
|
||||
CONFIG_BT_DEVICE_NAME="Hearing Aid sample"
|
||||
# Appearance: Generic Hearing aid (0x0A40)
|
||||
CONFIG_BT_DEVICE_APPEARANCE=2624
|
||||
|
||||
CONFIG_BT_AUDIO=y
|
||||
CONFIG_BT_AUDIO_UNICAST_SERVER=y
|
||||
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, Live
|
||||
CONFIG_BT_PACS_SNK_CONTEXT=0x0047
|
||||
# Source Contexts Supported: Unspecified, Conversational
|
||||
CONFIG_BT_PACS_SRC_CONTEXT=0x0003
|
||||
|
||||
CONFIG_BT_PAC_SNK_LOC=y
|
||||
CONFIG_BT_PAC_SRC_LOC=y
|
||||
|
||||
CONFIG_BT_VCS=y
|
||||
CONFIG_BT_MICP_MIC_DEV=y
|
||||
CONFIG_BT_HAS=y
|
||||
CONFIG_BT_HAS_HEARING_AID_MONAURAL=y
|
||||
CONFIG_BT_HAS_PRESET_COUNT=4
|
||||
CONFIG_BT_HAS_PRESET_NAME_DYNAMIC=y
|
||||
CONFIG_BT_CAP_ACCEPTOR=y
|
||||
|
||||
CONFIG_BT_AICS_MAX_INSTANCE_COUNT=2
|
||||
# The HA may expose an instance of MICS and an instance of AICS to control
|
||||
# the capture of ambient sound.
|
||||
CONFIG_BT_VCS_AICS_INSTANCE_COUNT=1
|
||||
# An HA may instantiate one or more instances of Audio Input Control Service
|
||||
# (AICS) to expose control of the gain of its inputs to a Volume Controller.
|
||||
CONFIG_BT_MICP_MIC_DEV_AICS_INSTANCE_COUNT=1
|
||||
|
||||
# If the HA supports the Volume Balance feature (see Section 3.1) and the HA
|
||||
# is part of a Binaural Hearing Aid Set, the HA shall instantiate one instance
|
||||
# of Volume Offset Control Service (VOCS).
|
||||
CONFIG_BT_VOCS_MAX_INSTANCE_COUNT=1
|
||||
CONFIG_BT_VCS_VOCS_INSTANCE_COUNT=1
|
||||
|
||||
CONFIG_BT_BAS=y
|
||||
CONFIG_BT_IAS=y
|
||||
|
||||
CONFIG_BT_TBS_CLIENT=y
|
||||
CONFIG_BT_TBS_CLIENT_CCID=y
|
||||
CONFIG_BT_TBS_CLIENT_STATUS_FLAGS=y
|
||||
|
||||
CONFIG_BT_DEBUG_LOG=y
|
||||
23
samples/bluetooth/hap_ha/sample.yaml
Normal file
23
samples/bluetooth/hap_ha/sample.yaml
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
sample:
|
||||
description: Bluetooth HAP Hearing Aid sample
|
||||
name: Bluetooth HAP Hearing Aid sample
|
||||
tests:
|
||||
sample.bluetooth.hap_ha.monaural:
|
||||
harness: bluetooth
|
||||
platform_allow: native_posix
|
||||
tags: bluetooth
|
||||
build_only: true
|
||||
sample.bluetooth.hap_ha.banded:
|
||||
harness: bluetooth
|
||||
platform_allow: native_posix
|
||||
tags: bluetooth
|
||||
extra_args: OVERLAY_CONFIG="banded.conf"
|
||||
build_only: true
|
||||
sample.bluetooth.hap_ha.binaural:
|
||||
harness: bluetooth
|
||||
platform_allow: native_posix
|
||||
tags: bluetooth
|
||||
extra_args: OVERLAY_CONFIG="binaural.conf"
|
||||
build_only: true
|
||||
extra_configs:
|
||||
- CONFIG_HAP_HA_SET_RANK=2
|
||||
437
samples/bluetooth/hap_ha/src/bap_unicast_sr.c
Normal file
437
samples/bluetooth/hap_ha/src/bap_unicast_sr.c
Normal file
|
|
@ -0,0 +1,437 @@
|
|||
/** @file
|
||||
* @brief Bluetooth Basic Audio Profile (BAP) Unicast Server role.
|
||||
*
|
||||
* Copyright (c) 2021-2022 Nordic Semiconductor ASA
|
||||
* Copyright (c) 2022 Codecoup
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <zephyr/zephyr.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/capabilities.h>
|
||||
|
||||
NET_BUF_POOL_FIXED_DEFINE(tx_pool, CONFIG_BT_ASCS_ASE_SRC_COUNT,
|
||||
BT_ISO_SDU_BUF_SIZE(CONFIG_BT_ISO_TX_MTU),
|
||||
8, NULL);
|
||||
|
||||
static struct bt_codec lc3_codec =
|
||||
BT_CODEC_LC3(BT_CODEC_LC3_FREQ_16KHZ | BT_CODEC_LC3_FREQ_24KHZ,
|
||||
BT_CODEC_LC3_DURATION_10,
|
||||
BT_CODEC_LC3_CHAN_COUNT_SUPPORT(1), 40u, 60u, 1u,
|
||||
(BT_AUDIO_CONTEXT_TYPE_CONVERSATIONAL | BT_AUDIO_CONTEXT_TYPE_MEDIA));
|
||||
|
||||
static struct bt_conn *default_conn;
|
||||
static struct k_work_delayable audio_send_work;
|
||||
static struct bt_audio_stream streams[CONFIG_BT_ASCS_ASE_SNK_COUNT + CONFIG_BT_ASCS_ASE_SRC_COUNT];
|
||||
static struct bt_audio_source {
|
||||
struct bt_audio_stream *stream;
|
||||
uint32_t seq_num;
|
||||
} source_streams[CONFIG_BT_ASCS_ASE_SRC_COUNT];
|
||||
static size_t configured_source_stream_count;
|
||||
|
||||
static uint32_t get_and_incr_seq_num(const struct bt_audio_stream *stream)
|
||||
{
|
||||
for (size_t i = 0U; i < configured_source_stream_count; i++) {
|
||||
if (stream == source_streams[i].stream) {
|
||||
return source_streams[i].seq_num++;
|
||||
}
|
||||
}
|
||||
|
||||
printk("Could not find endpoint from stream %p\n", stream);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
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(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);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Send audio data on timeout
|
||||
*
|
||||
* This will send an increasing amount of audio data, starting from 1 octet.
|
||||
* The data is just mock data, and does not actually represent any audio.
|
||||
*
|
||||
* First iteration : 0x00
|
||||
* Second iteration: 0x00 0x01
|
||||
* Third iteration : 0x00 0x01 0x02
|
||||
*
|
||||
* And so on, until it wraps around the configured MTU (CONFIG_BT_ISO_TX_MTU)
|
||||
*
|
||||
* @param work Pointer to the work structure
|
||||
*/
|
||||
static void audio_timer_timeout(struct k_work *work)
|
||||
{
|
||||
int ret;
|
||||
static uint8_t buf_data[CONFIG_BT_ISO_TX_MTU];
|
||||
static bool data_initialized;
|
||||
struct net_buf *buf;
|
||||
static size_t len_to_send = 1;
|
||||
|
||||
if (!data_initialized) {
|
||||
/* TODO: Actually encode some audio data */
|
||||
for (size_t i = 0U; i < ARRAY_SIZE(buf_data); i++) {
|
||||
buf_data[i] = (uint8_t)i;
|
||||
}
|
||||
|
||||
data_initialized = true;
|
||||
}
|
||||
|
||||
/* We configured the sink streams to be first in `streams`, so that
|
||||
* we can use `stream[i]` to select sink streams (i.e. streams with
|
||||
* data going to the server)
|
||||
*/
|
||||
for (size_t i = 0; i < configured_source_stream_count; i++) {
|
||||
struct bt_audio_stream *stream = source_streams[i].stream;
|
||||
|
||||
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);
|
||||
|
||||
ret = bt_audio_stream_send(stream, buf,
|
||||
get_and_incr_seq_num(stream),
|
||||
BT_ISO_TIMESTAMP_NONE);
|
||||
if (ret < 0) {
|
||||
printk("Failed to send audio data on streams[%zu] (%p): (%d)\n",
|
||||
i, stream, ret);
|
||||
net_buf_unref(buf);
|
||||
} else {
|
||||
printk("Sending mock data with len %zu on streams[%zu] (%p)\n",
|
||||
len_to_send, i, stream);
|
||||
}
|
||||
}
|
||||
|
||||
k_work_schedule(&audio_send_work, K_MSEC(1000U));
|
||||
|
||||
len_to_send++;
|
||||
if (len_to_send > ARRAY_SIZE(buf_data)) {
|
||||
len_to_send = 1;
|
||||
}
|
||||
}
|
||||
|
||||
static struct bt_audio_stream *lc3_config(struct bt_conn *conn, struct bt_audio_ep *ep,
|
||||
enum bt_audio_dir dir, struct bt_audio_capability *cap,
|
||||
struct bt_codec *codec)
|
||||
{
|
||||
printk("ASE Codec Config: conn %p ep %p dir %u, cap %p\n",
|
||||
conn, ep, dir, cap);
|
||||
|
||||
print_codec(codec);
|
||||
|
||||
for (size_t i = 0; i < ARRAY_SIZE(streams); i++) {
|
||||
struct bt_audio_stream *stream = &streams[i];
|
||||
|
||||
if (!stream->conn) {
|
||||
printk("ASE Codec Config stream %p\n", stream);
|
||||
if (dir == BT_AUDIO_DIR_SOURCE) {
|
||||
source_streams[configured_source_stream_count++].stream = stream;
|
||||
}
|
||||
|
||||
return stream;
|
||||
}
|
||||
}
|
||||
|
||||
printk("No streams available\n");
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int lc3_reconfig(struct bt_audio_stream *stream, struct bt_audio_capability *cap,
|
||||
struct bt_codec *codec)
|
||||
{
|
||||
printk("ASE Codec Reconfig: stream %p cap %p\n", stream, cap);
|
||||
|
||||
print_codec(codec);
|
||||
|
||||
/* We only support one QoS at the moment, reject changes */
|
||||
return -ENOEXEC;
|
||||
}
|
||||
|
||||
static int lc3_qos(struct bt_audio_stream *stream, struct bt_codec_qos *qos)
|
||||
{
|
||||
printk("QoS: stream %p qos %p\n", stream, qos);
|
||||
|
||||
print_qos(qos);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int lc3_enable(struct bt_audio_stream *stream, struct bt_codec_data *meta,
|
||||
size_t meta_count)
|
||||
{
|
||||
printk("Enable: stream %p meta_count %u\n", stream, meta_count);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int lc3_start(struct bt_audio_stream *stream)
|
||||
{
|
||||
printk("Start: stream %p\n", stream);
|
||||
|
||||
if (IS_ENABLED(CONFIG_BT_ASCS_ASE_SRC)) {
|
||||
for (size_t i = 0U; i < configured_source_stream_count; i++) {
|
||||
if (source_streams[i].stream == stream) {
|
||||
source_streams[i].seq_num = 0U;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (configured_source_stream_count > 0 &&
|
||||
!k_work_delayable_is_pending(&audio_send_work)) {
|
||||
|
||||
/* Start send timer */
|
||||
k_work_schedule(&audio_send_work, K_MSEC(0));
|
||||
}
|
||||
}
|
||||
|
||||
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_audio_stream *stream, struct bt_codec_data *meta,
|
||||
size_t meta_count)
|
||||
{
|
||||
printk("Metadata: stream %p meta_count %u\n", stream, meta_count);
|
||||
|
||||
for (size_t i = 0; i < meta_count; i++) {
|
||||
if (!valid_metadata_type(meta->data.type, meta->data.data_len)) {
|
||||
printk("Invalid metadata type %u or length %u\n",
|
||||
meta->data.type, meta->data.data_len);
|
||||
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int lc3_disable(struct bt_audio_stream *stream)
|
||||
{
|
||||
printk("Disable: stream %p\n", stream);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int lc3_stop(struct bt_audio_stream *stream)
|
||||
{
|
||||
printk("Stop: stream %p\n", stream);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int lc3_release(struct bt_audio_stream *stream)
|
||||
{
|
||||
printk("Release: stream %p\n", stream);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct bt_audio_capability_ops lc3_ops = {
|
||||
.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_audio_stream *stream, const struct bt_iso_recv_info *info,
|
||||
struct net_buf *buf)
|
||||
{
|
||||
printk("Incoming audio on stream %p len %u\n", stream, buf->len);
|
||||
}
|
||||
|
||||
static struct bt_audio_stream_ops stream_ops = {
|
||||
.recv = stream_recv
|
||||
};
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
static void disconnected(struct bt_conn *conn, uint8_t reason)
|
||||
{
|
||||
char addr[BT_ADDR_LE_STR_LEN];
|
||||
struct k_work_sync sync;
|
||||
|
||||
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;
|
||||
|
||||
if (IS_ENABLED(CONFIG_BT_ASCS_ASE_SRC)) {
|
||||
/* reset data */
|
||||
(void)memset(source_streams, 0, sizeof(source_streams));
|
||||
configured_source_stream_count = 0U;
|
||||
k_work_cancel_delayable_sync(&audio_send_work, &sync);
|
||||
}
|
||||
}
|
||||
|
||||
BT_CONN_CB_DEFINE(conn_callbacks) = {
|
||||
.connected = connected,
|
||||
.disconnected = disconnected,
|
||||
};
|
||||
|
||||
static struct bt_audio_capability caps[] = {
|
||||
{
|
||||
.dir = BT_AUDIO_DIR_SINK,
|
||||
.pref = BT_AUDIO_CAPABILITY_PREF(
|
||||
BT_AUDIO_CAPABILITY_UNFRAMED_SUPPORTED,
|
||||
BT_GAP_LE_PHY_2M, 0x02, 10, 20000, 40000,
|
||||
20000, 40000),
|
||||
.codec = &lc3_codec,
|
||||
.ops = &lc3_ops,
|
||||
},
|
||||
#if defined(CONFIG_BT_ASCS_ASE_SRC)
|
||||
{
|
||||
.dir = BT_AUDIO_DIR_SOURCE,
|
||||
.pref = BT_AUDIO_CAPABILITY_PREF(
|
||||
BT_AUDIO_CAPABILITY_UNFRAMED_SUPPORTED,
|
||||
BT_GAP_LE_PHY_2M, 0x02, 10, 20000, 40000,
|
||||
20000, 40000),
|
||||
.codec = &lc3_codec,
|
||||
.ops = &lc3_ops,
|
||||
}
|
||||
#endif /* CONFIG_BT_ASCS_ASE_SRC */
|
||||
};
|
||||
|
||||
int bap_unicast_sr_init(void)
|
||||
{
|
||||
for (size_t i = 0; i < ARRAY_SIZE(caps); i++) {
|
||||
bt_audio_capability_register(&caps[i]);
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < ARRAY_SIZE(streams); i++) {
|
||||
bt_audio_stream_cb_register(&streams[i], &stream_ops);
|
||||
}
|
||||
|
||||
if (IS_ENABLED(CONFIG_BT_ASCS_ASE_SRC)) {
|
||||
k_work_init_delayable(&audio_send_work, audio_timer_timeout);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
160
samples/bluetooth/hap_ha/src/ccp_call_ctrl.c
Normal file
160
samples/bluetooth/hap_ha/src/ccp_call_ctrl.c
Normal file
|
|
@ -0,0 +1,160 @@
|
|||
/** @file
|
||||
* @brief Bluetooth Call Control Profile (CCP) Call Controller role.
|
||||
*
|
||||
* Copyright (c) 2020 Nordic Semiconductor ASA
|
||||
* Copyright (c) 2022 Codecoup
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <zephyr/zephyr.h>
|
||||
#include <zephyr/sys/printk.h>
|
||||
#include <zephyr/sys/util.h>
|
||||
|
||||
#include <zephyr/bluetooth/conn.h>
|
||||
#include <zephyr/bluetooth/audio/tbs.h>
|
||||
|
||||
enum {
|
||||
CCP_FLAG_PROFILE_CONNECTED,
|
||||
CCP_FLAG_GTBS_DISCOVER,
|
||||
CCP_FLAG_CCID_READ,
|
||||
CCP_FLAG_STATUS_FLAGS_READ,
|
||||
CCP_FLAG_CALL_STATE_READ,
|
||||
|
||||
CCP_FLAG_NUM,
|
||||
};
|
||||
|
||||
static ATOMIC_DEFINE(flags, CCP_FLAG_NUM)[CONFIG_BT_MAX_CONN];
|
||||
|
||||
static int process_profile_connection(struct bt_conn *conn)
|
||||
{
|
||||
atomic_t *flags_for_conn = flags[bt_conn_index(conn)];
|
||||
int err = 0;
|
||||
|
||||
if (!atomic_test_and_set_bit(flags_for_conn, CCP_FLAG_GTBS_DISCOVER)) {
|
||||
err = bt_tbs_client_discover(conn, true);
|
||||
if (err != 0) {
|
||||
printk("bt_tbs_client_discover (err %d)\n", err);
|
||||
}
|
||||
} else if (!atomic_test_and_set_bit(flags_for_conn, CCP_FLAG_CCID_READ)) {
|
||||
err = bt_tbs_client_read_ccid(conn, BT_TBS_GTBS_INDEX);
|
||||
if (err != 0) {
|
||||
printk("bt_tbs_client_read_ccid (err %d)\n", err);
|
||||
}
|
||||
} else if (!atomic_test_and_set_bit(flags_for_conn, CCP_FLAG_STATUS_FLAGS_READ)) {
|
||||
err = bt_tbs_client_read_status_flags(conn, BT_TBS_GTBS_INDEX);
|
||||
if (err != 0) {
|
||||
printk("bt_tbs_client_read_status_flags (err %d)\n", err);
|
||||
}
|
||||
} else if (!atomic_test_and_set_bit(flags_for_conn, CCP_FLAG_CALL_STATE_READ)) {
|
||||
err = bt_tbs_client_read_call_state(conn, BT_TBS_GTBS_INDEX);
|
||||
if (err != 0) {
|
||||
printk("bt_tbs_client_read_call_state (err %d)\n", err);
|
||||
}
|
||||
} else if (!atomic_test_and_set_bit(flags_for_conn, CCP_FLAG_PROFILE_CONNECTED)) {
|
||||
printk("CCP Profile connected\n");
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static void connected(struct bt_conn *conn, uint8_t err)
|
||||
{
|
||||
if (err) {
|
||||
printk("Connection failed (err %d)\n", err);
|
||||
return;
|
||||
}
|
||||
|
||||
atomic_set(flags[bt_conn_index(conn)], 0);
|
||||
|
||||
if (process_profile_connection(conn) != 0) {
|
||||
printk("Profile connection failed");
|
||||
}
|
||||
}
|
||||
|
||||
BT_CONN_CB_DEFINE(conn_callbacks) = {
|
||||
.connected = connected,
|
||||
};
|
||||
|
||||
static void discover_cb(struct bt_conn *conn, int err, uint8_t tbs_count, bool gtbs_found)
|
||||
{
|
||||
if (!gtbs_found) {
|
||||
printk("Failed to discover GTBS\n");
|
||||
return;
|
||||
}
|
||||
|
||||
if (err) {
|
||||
printk("%s (err %d)\n", __func__, err);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!atomic_test_bit(flags[bt_conn_index(conn)], CCP_FLAG_PROFILE_CONNECTED)) {
|
||||
process_profile_connection(conn);
|
||||
}
|
||||
}
|
||||
|
||||
static void ccid_cb(struct bt_conn *conn, int err, uint8_t inst_index, uint32_t value)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
if (!atomic_test_bit(flags[bt_conn_index(conn)], CCP_FLAG_PROFILE_CONNECTED)) {
|
||||
process_profile_connection(conn);
|
||||
}
|
||||
}
|
||||
|
||||
static void status_flags_cb(struct bt_conn *conn, int err, uint8_t inst_index, uint32_t value)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
if (!atomic_test_bit(flags[bt_conn_index(conn)], CCP_FLAG_PROFILE_CONNECTED)) {
|
||||
process_profile_connection(conn);
|
||||
}
|
||||
}
|
||||
|
||||
static void call_state_cb(struct bt_conn *conn, int err, uint8_t inst_index, uint8_t call_count,
|
||||
const struct bt_tbs_client_call_state *call_states)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
if (!atomic_test_bit(flags[bt_conn_index(conn)], CCP_FLAG_PROFILE_CONNECTED)) {
|
||||
process_profile_connection(conn);
|
||||
}
|
||||
}
|
||||
|
||||
struct bt_tbs_client_cb tbs_client_cb = {
|
||||
.discover = discover_cb,
|
||||
.ccid = ccid_cb,
|
||||
.status_flags = status_flags_cb,
|
||||
.call_state = call_state_cb,
|
||||
};
|
||||
|
||||
int ccp_call_ctrl_init(void)
|
||||
{
|
||||
bt_tbs_client_register_cb(&tbs_client_cb);
|
||||
|
||||
return 0;
|
||||
}
|
||||
48
samples/bluetooth/hap_ha/src/csip_set_member.c
Normal file
48
samples/bluetooth/hap_ha/src/csip_set_member.c
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
/** @file
|
||||
* @brief Bluetooth Coordinated Set Identifier Profile (CSIP) Set Member role.
|
||||
*
|
||||
* Copyright (c) 2022 Codecoup
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <zephyr/zephyr.h>
|
||||
#include <zephyr/sys/printk.h>
|
||||
|
||||
#include <zephyr/bluetooth/audio/cap.h>
|
||||
#include <zephyr/bluetooth/audio/csis.h>
|
||||
|
||||
#define CSIS_SIRK_DEBUG { 0xcd, 0xcc, 0x72, 0xdd, 0x86, 0x8c, 0xcd, 0xce, \
|
||||
0x22, 0xfd, 0xa1, 0x21, 0x09, 0x7d, 0x7d, 0x45 }
|
||||
|
||||
static struct bt_csis *csis;
|
||||
|
||||
static void csis_lock_changed_cb(struct bt_conn *conn, struct bt_csis *csis, bool locked)
|
||||
{
|
||||
printk("Client %p %s the lock\n", conn, locked ? "locked" : "released");
|
||||
}
|
||||
|
||||
static uint8_t sirk_read_req_cb(struct bt_conn *conn, struct bt_csis *csis)
|
||||
{
|
||||
return BT_CSIS_READ_SIRK_REQ_RSP_ACCEPT;
|
||||
}
|
||||
|
||||
static struct bt_csis_cb csis_cb = {
|
||||
.lock_changed = csis_lock_changed_cb,
|
||||
.sirk_read_req = sirk_read_req_cb,
|
||||
};
|
||||
|
||||
int csip_set_member_init(void (*rsi_changed)(const uint8_t *rsi))
|
||||
{
|
||||
struct bt_csis_register_param param = {
|
||||
.set_size = 2,
|
||||
.rank = CONFIG_HAP_HA_SET_RANK,
|
||||
.lockable = false,
|
||||
.set_sirk = CSIS_SIRK_DEBUG,
|
||||
.cb = &csis_cb,
|
||||
};
|
||||
|
||||
csis_cb.rsi_changed = rsi_changed;
|
||||
|
||||
return bt_cap_acceptor_register(¶m, &csis);
|
||||
}
|
||||
51
samples/bluetooth/hap_ha/src/hap_ha.h
Normal file
51
samples/bluetooth/hap_ha/src/hap_ha.h
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
/*
|
||||
* Copyright (c) 2022 Codecoup
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <zephyr/types.h>
|
||||
|
||||
/**
|
||||
* @brief Initialize the BAP Unicast Server role
|
||||
*
|
||||
* @return 0 if success, errno on failure.
|
||||
*/
|
||||
int bap_unicast_sr_init(void);
|
||||
|
||||
/**
|
||||
* @brief Initialize the CSIP Set Member role
|
||||
*
|
||||
* @return 0 if success, errno on failure.
|
||||
*/
|
||||
int csip_set_member_init(void (*rsi_changed)(const uint8_t *rsi));
|
||||
|
||||
/**
|
||||
* @brief Initialize the VCP Volume Renderer role
|
||||
*
|
||||
* @return 0 if success, errno on failure.
|
||||
*/
|
||||
int vcp_vol_renderer_init(void);
|
||||
|
||||
/**
|
||||
* @brief Initialize the MICP Microphone Device role
|
||||
*
|
||||
* @return 0 if success, errno on failure.
|
||||
*/
|
||||
int micp_mic_dev_init(void);
|
||||
|
||||
/**
|
||||
* @brief Initialize the CCP Call Control Client role
|
||||
*
|
||||
* @return 0 if success, errno on failure.
|
||||
*/
|
||||
int ccp_call_ctrl_init(void);
|
||||
|
||||
/**
|
||||
* @brief Initialize the HAS Server
|
||||
*
|
||||
* This will register hearing aid sample presets.
|
||||
*
|
||||
* @return 0 if success, errno on failure.
|
||||
*/
|
||||
int has_server_init(void);
|
||||
76
samples/bluetooth/hap_ha/src/has_server.c
Normal file
76
samples/bluetooth/hap_ha/src/has_server.c
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
/** @file
|
||||
* @brief Bluetooth Hearing Access Service (HAS) Server role.
|
||||
*
|
||||
* Copyright (c) 2022 Codecoup
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <zephyr/zephyr.h>
|
||||
#include <zephyr/sys/printk.h>
|
||||
#include <zephyr/sys/util.h>
|
||||
|
||||
#include <zephyr/bluetooth/audio/has.h>
|
||||
|
||||
#define PRESET_INDEX_UNIVERSAL 1
|
||||
#define PRESET_INDEX_OUTDOOR 5
|
||||
#define PRESET_INDEX_NOISY_ENV 8
|
||||
#define PRESET_INDEX_OFFICE 22
|
||||
|
||||
static int select_cb(uint8_t index, bool sync)
|
||||
{
|
||||
printk("%s %u sync %d", __func__, index, sync);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void name_changed_cb(uint8_t index, const char *name)
|
||||
{
|
||||
printk("%s %u name %s", __func__, index, name);
|
||||
}
|
||||
|
||||
static const struct bt_has_preset_ops ops = {
|
||||
.select = select_cb,
|
||||
.name_changed = name_changed_cb,
|
||||
};
|
||||
|
||||
int has_server_init(void)
|
||||
{
|
||||
int err;
|
||||
|
||||
struct bt_has_preset_register_param param[] = {
|
||||
{
|
||||
.index = PRESET_INDEX_UNIVERSAL,
|
||||
.properties = BT_HAS_PROP_AVAILABLE | BT_HAS_PROP_WRITABLE,
|
||||
.name = "Universal",
|
||||
.ops = &ops,
|
||||
},
|
||||
{
|
||||
.index = PRESET_INDEX_OUTDOOR,
|
||||
.properties = BT_HAS_PROP_AVAILABLE | BT_HAS_PROP_WRITABLE,
|
||||
.name = "Outdoor",
|
||||
.ops = &ops,
|
||||
},
|
||||
{
|
||||
.index = PRESET_INDEX_NOISY_ENV,
|
||||
.properties = BT_HAS_PROP_AVAILABLE | BT_HAS_PROP_WRITABLE,
|
||||
.name = "Noisy environment",
|
||||
.ops = &ops,
|
||||
},
|
||||
{
|
||||
.index = PRESET_INDEX_OFFICE,
|
||||
.properties = BT_HAS_PROP_AVAILABLE | BT_HAS_PROP_WRITABLE,
|
||||
.name = "Office",
|
||||
.ops = &ops,
|
||||
},
|
||||
};
|
||||
|
||||
for (size_t i = 0; i < ARRAY_SIZE(param); i++) {
|
||||
err = bt_has_preset_register(¶m[i]);
|
||||
if (err != 0) {
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
234
samples/bluetooth/hap_ha/src/main.c
Normal file
234
samples/bluetooth/hap_ha/src/main.c
Normal file
|
|
@ -0,0 +1,234 @@
|
|||
/*
|
||||
* Copyright (c) 2022 Codecoup
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <zephyr/zephyr.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/capabilities.h>
|
||||
#include <zephyr/bluetooth/audio/csis.h>
|
||||
#include <zephyr/bluetooth/services/ias.h>
|
||||
|
||||
#include "hap_ha.h"
|
||||
|
||||
#define MANDATORY_SINK_CONTEXT (BT_AUDIO_CONTEXT_TYPE_UNSPECIFIED | \
|
||||
BT_AUDIO_CONTEXT_TYPE_CONVERSATIONAL | \
|
||||
BT_AUDIO_CONTEXT_TYPE_MEDIA | \
|
||||
BT_AUDIO_CONTEXT_TYPE_LIVE)
|
||||
|
||||
#define AVAILABLE_SINK_CONTEXT CONFIG_BT_PACS_SNK_CONTEXT
|
||||
#define AVAILABLE_SOURCE_CONTEXT CONFIG_BT_PACS_SRC_CONTEXT
|
||||
|
||||
BUILD_ASSERT((CONFIG_BT_PACS_SNK_CONTEXT & MANDATORY_SINK_CONTEXT) == MANDATORY_SINK_CONTEXT,
|
||||
"Need to support mandatory Supported_Sink_Contexts");
|
||||
|
||||
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),
|
||||
(((AVAILABLE_SOURCE_CONTEXT) >> 0) & 0xFF),
|
||||
(((AVAILABLE_SOURCE_CONTEXT) >> 8) & 0xFF),
|
||||
0x00, /* Metadata length */
|
||||
};
|
||||
|
||||
static uint8_t csis_rsi_addata[BT_CSIS_RSI_SIZE];
|
||||
|
||||
/* 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_UUID16_ALL, BT_UUID_16_ENCODE(BT_UUID_ASCS_VAL)),
|
||||
#if defined(CONFIG_BT_CSIS)
|
||||
BT_DATA(BT_DATA_CSIS_RSI, csis_rsi_addata, ARRAY_SIZE(csis_rsi_addata)),
|
||||
#endif /* CONFIG_BT_CSIS */
|
||||
BT_DATA(BT_DATA_SVC_DATA16, unicast_server_addata, ARRAY_SIZE(unicast_server_addata)),
|
||||
};
|
||||
|
||||
static struct k_work_delayable adv_work;
|
||||
static struct bt_le_ext_adv *adv;
|
||||
|
||||
static void disconnected(struct bt_conn *conn, uint8_t reason)
|
||||
{
|
||||
/* Restart advertising after disconnection */
|
||||
k_work_schedule(&adv_work, K_SECONDS(1));
|
||||
}
|
||||
|
||||
BT_CONN_CB_DEFINE(conn_callbacks) = {
|
||||
.disconnected = disconnected,
|
||||
};
|
||||
|
||||
static void rsi_changed_cb(const uint8_t *rsi)
|
||||
{
|
||||
char rsi_str[13];
|
||||
|
||||
snprintk(rsi_str, 163, "%02x%02x%02x%02x%02x%02x",
|
||||
rsi[0], rsi[1], rsi[2], rsi[3], rsi[4], rsi[5]);
|
||||
|
||||
printk("PRSI: 0x%s\n", rsi_str);
|
||||
|
||||
memcpy(csis_rsi_addata, rsi, sizeof(csis_rsi_addata));
|
||||
|
||||
if (adv != NULL) {
|
||||
int err;
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if defined(CONFIG_BT_PRIVACY) && defined(CONFIG_BT_CSIS)
|
||||
static bool adv_rpa_expired_cb(struct bt_le_ext_adv *adv)
|
||||
{
|
||||
printk("%s", __func__);
|
||||
|
||||
/* TODO: Generate new RSI and update the advertisement data */
|
||||
|
||||
return true;
|
||||
}
|
||||
#endif /* CONFIG_BT_PRIVACY && CONFIG_BT_CSIS */
|
||||
|
||||
static const struct bt_le_ext_adv_cb adv_cb = {
|
||||
#if defined(CONFIG_BT_PRIVACY) && defined(CONFIG_BT_CSIS)
|
||||
.rpa_expired = adv_rpa_expired_cb,
|
||||
#endif /* CONFIG_BT_PRIVACY && CONFIG_BT_CSIS */
|
||||
};
|
||||
|
||||
static void adv_work_handler(struct k_work *work)
|
||||
{
|
||||
int err;
|
||||
|
||||
if (adv == NULL) {
|
||||
/* Create a non-connectable non-scannable advertising set */
|
||||
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);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
__ASSERT_NO_MSG(err == 0);
|
||||
}
|
||||
|
||||
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);
|
||||
} else {
|
||||
printk("Advertising successfully started\n");
|
||||
}
|
||||
}
|
||||
|
||||
#if defined(CONFIG_BT_IAS)
|
||||
static void alert_stop(void)
|
||||
{
|
||||
printk("Alert stopped\n");
|
||||
}
|
||||
|
||||
static void alert_start(void)
|
||||
{
|
||||
printk("Mild alert started\n");
|
||||
}
|
||||
|
||||
static void alert_high_start(void)
|
||||
{
|
||||
printk("High alert started\n");
|
||||
}
|
||||
|
||||
BT_IAS_CB_DEFINE(ias_callbacks) = {
|
||||
.no_alert = alert_stop,
|
||||
.mild_alert = alert_start,
|
||||
.high_alert = alert_high_start,
|
||||
};
|
||||
#endif /* CONFIG_BT_IAS */
|
||||
|
||||
void main(void)
|
||||
{
|
||||
int err;
|
||||
|
||||
err = bt_enable(NULL);
|
||||
if (err != 0) {
|
||||
printk("Bluetooth init failed (err %d)\n", err);
|
||||
return;
|
||||
}
|
||||
|
||||
printk("Bluetooth initialized\n");
|
||||
|
||||
err = has_server_init();
|
||||
if (err != 0) {
|
||||
printk("HAS Server init failed (err %d)\n", err);
|
||||
return;
|
||||
}
|
||||
|
||||
err = bap_unicast_sr_init();
|
||||
if (err != 0) {
|
||||
printk("BAP Unicast Server init failed (err %d)\n", err);
|
||||
return;
|
||||
}
|
||||
|
||||
if (IS_ENABLED(CONFIG_BT_HAS_HEARING_AID_BINAURAL)) {
|
||||
err = csip_set_member_init(rsi_changed_cb);
|
||||
if (err != 0) {
|
||||
printk("CSIP Set Member init failed (err %d)\n", err);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
err = vcp_vol_renderer_init();
|
||||
if (err != 0) {
|
||||
printk("VCP Volume Renderer init failed (err %d)\n", err);
|
||||
return;
|
||||
}
|
||||
|
||||
if (IS_ENABLED(CONFIG_BT_ASCS_ASE_SRC)) {
|
||||
err = micp_mic_dev_init();
|
||||
if (err != 0) {
|
||||
printk("MICP Microphone Device init failed (err %d)\n", err);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (IS_ENABLED(CONFIG_BT_TBS_CLIENT)) {
|
||||
err = ccp_call_ctrl_init();
|
||||
if (err != 0) {
|
||||
printk("MICP Microphone Device init failed (err %d)\n", err);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (IS_ENABLED(CONFIG_BT_HAS_HEARING_AID_BANDED)) {
|
||||
/* HAP_d1.0r00; 3.7 BAP Unicast Server role requirements
|
||||
* A Banded Hearing Aid in the HA role shall set the
|
||||
* Front Left and the Front Right bits to a value of 0b1
|
||||
* in the Sink Audio Locations characteristic value.
|
||||
*/
|
||||
bt_audio_capability_set_location(BT_AUDIO_DIR_SINK,
|
||||
(BT_AUDIO_LOCATION_FRONT_LEFT |
|
||||
BT_AUDIO_LOCATION_FRONT_RIGHT));
|
||||
} else {
|
||||
bt_audio_capability_set_location(BT_AUDIO_DIR_SINK,
|
||||
BT_AUDIO_LOCATION_FRONT_LEFT);
|
||||
}
|
||||
|
||||
bt_audio_capability_set_available_contexts(BT_AUDIO_DIR_SINK,
|
||||
AVAILABLE_SINK_CONTEXT);
|
||||
|
||||
if (IS_ENABLED(CONFIG_BT_ASCS_ASE_SRC)) {
|
||||
bt_audio_capability_set_location(BT_AUDIO_DIR_SOURCE,
|
||||
BT_AUDIO_LOCATION_FRONT_LEFT);
|
||||
bt_audio_capability_set_available_contexts(BT_AUDIO_DIR_SOURCE,
|
||||
AVAILABLE_SOURCE_CONTEXT);
|
||||
}
|
||||
|
||||
k_work_init_delayable(&adv_work, adv_work_handler);
|
||||
k_work_schedule(&adv_work, K_NO_WAIT);
|
||||
}
|
||||
128
samples/bluetooth/hap_ha/src/micp_mic_dev.c
Normal file
128
samples/bluetooth/hap_ha/src/micp_mic_dev.c
Normal file
|
|
@ -0,0 +1,128 @@
|
|||
/** @file
|
||||
* @brief Bluetooth Microphone Input Control Profile (MICP) Microphone Device role.
|
||||
*
|
||||
* Copyright (c) 2020 Bose Corporation
|
||||
* Copyright (c) 2020-2022 Nordic Semiconductor ASA
|
||||
* Copyright (c) 2022 Codecoup
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <zephyr/zephyr.h>
|
||||
#include <zephyr/sys/printk.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include <zephyr/bluetooth/conn.h>
|
||||
#include <zephyr/bluetooth/audio/micp.h>
|
||||
|
||||
static void micp_mic_dev_mute_cb(uint8_t mute)
|
||||
{
|
||||
printk("Mute value %u\n", mute);
|
||||
}
|
||||
|
||||
static struct bt_micp_mic_dev_cb micp_mic_dev_cbs = {
|
||||
.mute = micp_mic_dev_mute_cb,
|
||||
};
|
||||
|
||||
#if defined(CONFIG_BT_MICP_MIC_DEV_AICS)
|
||||
static struct bt_micp_included micp_included;
|
||||
|
||||
static void micp_mic_dev_aics_state_cb(struct bt_aics *inst, int err, int8_t gain, uint8_t mute,
|
||||
uint8_t mode)
|
||||
{
|
||||
if (err != 0) {
|
||||
printk("AICS state get failed (%d) for inst %p\n", err, inst);
|
||||
} else {
|
||||
printk("AICS inst %p state gain %d, mute %u, mode %u\n",
|
||||
inst, gain, mute, mode);
|
||||
}
|
||||
|
||||
}
|
||||
static void micp_mic_dev_aics_gain_setting_cb(struct bt_aics *inst, int err, uint8_t units,
|
||||
int8_t minimum, int8_t maximum)
|
||||
{
|
||||
if (err != 0) {
|
||||
printk("AICS gain settings get failed (%d) for inst %p\n", err, inst);
|
||||
} else {
|
||||
printk("AICS inst %p gain settings units %u, min %d, max %d\n",
|
||||
inst, units, minimum, maximum);
|
||||
}
|
||||
|
||||
}
|
||||
static void micp_mic_dev_aics_input_type_cb(struct bt_aics *inst, int err, uint8_t input_type)
|
||||
{
|
||||
if (err != 0) {
|
||||
printk("AICS input type get failed (%d) for inst %p\n", err, inst);
|
||||
} else {
|
||||
printk("AICS inst %p input type %u\n", inst, input_type);
|
||||
}
|
||||
|
||||
}
|
||||
static void micp_mic_dev_aics_status_cb(struct bt_aics *inst, int err, bool active)
|
||||
{
|
||||
if (err != 0) {
|
||||
printk("AICS status get failed (%d) for inst %p\n", err, inst);
|
||||
} else {
|
||||
printk("AICS inst %p status %s\n", inst, active ? "active" : "inactive");
|
||||
}
|
||||
|
||||
}
|
||||
static void micp_mic_dev_aics_description_cb(struct bt_aics *inst, int err, char *description)
|
||||
{
|
||||
if (err != 0) {
|
||||
printk("AICS description get failed (%d) for inst %p\n", err, inst);
|
||||
} else {
|
||||
printk("AICS inst %p description %s\n", inst, description);
|
||||
}
|
||||
}
|
||||
|
||||
static struct bt_aics_cb aics_cb = {
|
||||
.state = micp_mic_dev_aics_state_cb,
|
||||
.gain_setting = micp_mic_dev_aics_gain_setting_cb,
|
||||
.type = micp_mic_dev_aics_input_type_cb,
|
||||
.status = micp_mic_dev_aics_status_cb,
|
||||
.description = micp_mic_dev_aics_description_cb,
|
||||
};
|
||||
#endif /* CONFIG_BT_MICP_MIC_DEV_AICS */
|
||||
|
||||
int micp_mic_dev_init(void)
|
||||
{
|
||||
int err;
|
||||
struct bt_micp_mic_dev_register_param micp_param;
|
||||
|
||||
(void)memset(&micp_param, 0, sizeof(micp_param));
|
||||
|
||||
#if defined(CONFIG_BT_MICP_MIC_DEV_AICS)
|
||||
char input_desc[CONFIG_BT_MICP_MIC_DEV_AICS_INSTANCE_COUNT][16];
|
||||
|
||||
for (int i = 0; i < ARRAY_SIZE(micp_param.aics_param); i++) {
|
||||
micp_param.aics_param[i].desc_writable = true;
|
||||
snprintf(input_desc[i], sizeof(input_desc[i]), "Input %d", i + 1);
|
||||
micp_param.aics_param[i].description = input_desc[i];
|
||||
micp_param.aics_param[i].type = BT_AICS_INPUT_TYPE_UNSPECIFIED;
|
||||
micp_param.aics_param[i].status = true;
|
||||
micp_param.aics_param[i].gain_mode = BT_AICS_MODE_MANUAL;
|
||||
micp_param.aics_param[i].units = 1;
|
||||
micp_param.aics_param[i].min_gain = -100;
|
||||
micp_param.aics_param[i].max_gain = 100;
|
||||
micp_param.aics_param[i].cb = &aics_cb;
|
||||
}
|
||||
#endif /* CONFIG_BT_MICP_MIC_DEV_AICS */
|
||||
|
||||
micp_param.cb = &micp_mic_dev_cbs;
|
||||
|
||||
err = bt_micp_mic_dev_register(&micp_param);
|
||||
if (err != 0) {
|
||||
return err;
|
||||
}
|
||||
|
||||
#if defined(CONFIG_BT_MICP_MIC_DEV_AICS)
|
||||
err = bt_micp_mic_dev_included_get(&micp_included);
|
||||
if (err != 0) {
|
||||
return err;
|
||||
}
|
||||
#endif /* CONFIG_BT_MICP_MIC_DEV_AICS */
|
||||
|
||||
return 0;
|
||||
}
|
||||
179
samples/bluetooth/hap_ha/src/vcp_vol_renderer.c
Normal file
179
samples/bluetooth/hap_ha/src/vcp_vol_renderer.c
Normal file
|
|
@ -0,0 +1,179 @@
|
|||
/** @file
|
||||
* @brief Bluetooth Volume Control Profile (VCP) Volume Renderer role.
|
||||
*
|
||||
* Copyright (c) 2020 Bose Corporation
|
||||
* Copyright (c) 2020-2021 Nordic Semiconductor ASA
|
||||
* Copyright (c) 2022 Codecoup
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <zephyr/zephyr.h>
|
||||
#include <zephyr/sys/printk.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include <zephyr/bluetooth/conn.h>
|
||||
#include <zephyr/bluetooth/audio/vcs.h>
|
||||
|
||||
static struct bt_vcs *vcs;
|
||||
static struct bt_vcs_included vcs_included;
|
||||
|
||||
static void vcs_state_cb(struct bt_vcs *vcs, 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(struct bt_vcs *vcs, int err, uint8_t flags)
|
||||
{
|
||||
if (err) {
|
||||
printk("VCS flags get failed (%d)\n", err);
|
||||
} else {
|
||||
printk("VCS flags 0x%02X\n", flags);
|
||||
}
|
||||
}
|
||||
|
||||
static void aics_state_cb(struct bt_aics *inst, int err, int8_t gain, uint8_t mute, uint8_t mode)
|
||||
{
|
||||
if (err) {
|
||||
printk("AICS state get failed (%d) for inst %p\n", err, inst);
|
||||
} else {
|
||||
printk("AICS inst %p state gain %d, mute %u, mode %u\n",
|
||||
inst, gain, mute, mode);
|
||||
}
|
||||
}
|
||||
|
||||
static void aics_gain_setting_cb(struct bt_aics *inst, int err, uint8_t units, int8_t minimum,
|
||||
int8_t maximum)
|
||||
{
|
||||
if (err) {
|
||||
printk("AICS gain settings get failed (%d) for inst %p\n", err, inst);
|
||||
} else {
|
||||
printk("AICS inst %p gain settings units %u, min %d, max %d\n",
|
||||
inst, units, minimum, maximum);
|
||||
}
|
||||
}
|
||||
|
||||
static void aics_input_type_cb(struct bt_aics *inst, int err, uint8_t input_type)
|
||||
{
|
||||
if (err) {
|
||||
printk("AICS input type get failed (%d) for inst %p\n", err, inst);
|
||||
} else {
|
||||
printk("AICS inst %p input type %u\n", inst, input_type);
|
||||
}
|
||||
}
|
||||
|
||||
static void aics_status_cb(struct bt_aics *inst, int err, bool active)
|
||||
{
|
||||
if (err) {
|
||||
printk("AICS status get failed (%d) for inst %p\n", err, inst);
|
||||
} else {
|
||||
printk("AICS inst %p status %s\n", inst, active ? "active" : "inactive");
|
||||
}
|
||||
|
||||
}
|
||||
static void aics_description_cb(struct bt_aics *inst, int err, char *description)
|
||||
{
|
||||
if (err) {
|
||||
printk("AICS description get failed (%d) for inst %p\n", err, inst);
|
||||
} else {
|
||||
printk("AICS inst %p description %s\n", inst, description);
|
||||
}
|
||||
}
|
||||
static void vocs_state_cb(struct bt_vocs *inst, int err, int16_t offset)
|
||||
{
|
||||
if (err) {
|
||||
printk("VOCS state get failed (%d) for inst %p\n", err, inst);
|
||||
} else {
|
||||
printk("VOCS inst %p offset %d\n", inst, offset);
|
||||
}
|
||||
}
|
||||
|
||||
static void vocs_location_cb(struct bt_vocs *inst, int err, uint32_t location)
|
||||
{
|
||||
if (err) {
|
||||
printk("VOCS location get failed (%d) for inst %p\n", err, inst);
|
||||
} else {
|
||||
printk("VOCS inst %p location %u\n", inst, location);
|
||||
}
|
||||
}
|
||||
|
||||
static void vocs_description_cb(struct bt_vocs *inst, int err, char *description)
|
||||
{
|
||||
if (err) {
|
||||
printk("VOCS description get failed (%d) for inst %p\n", err, inst);
|
||||
} else {
|
||||
printk("VOCS inst %p description %s\n", inst, description);
|
||||
}
|
||||
}
|
||||
|
||||
static struct bt_vcs_cb vcs_cbs = {
|
||||
.state = vcs_state_cb,
|
||||
.flags = vcs_flags_cb,
|
||||
};
|
||||
|
||||
static struct bt_aics_cb aics_cbs = {
|
||||
.state = aics_state_cb,
|
||||
.gain_setting = aics_gain_setting_cb,
|
||||
.type = aics_input_type_cb,
|
||||
.status = aics_status_cb,
|
||||
.description = aics_description_cb
|
||||
};
|
||||
|
||||
static struct bt_vocs_cb vocs_cbs = {
|
||||
.state = vocs_state_cb,
|
||||
.location = vocs_location_cb,
|
||||
.description = vocs_description_cb
|
||||
};
|
||||
|
||||
int vcp_vol_renderer_init(void)
|
||||
{
|
||||
int err;
|
||||
struct bt_vcs_register_param vcs_param;
|
||||
char input_desc[CONFIG_BT_VCS_AICS_INSTANCE_COUNT][16];
|
||||
char output_desc[CONFIG_BT_VCS_VOCS_INSTANCE_COUNT][16];
|
||||
|
||||
memset(&vcs_param, 0, sizeof(vcs_param));
|
||||
|
||||
for (int i = 0; i < ARRAY_SIZE(vcs_param.vocs_param); i++) {
|
||||
vcs_param.vocs_param[i].location_writable = true;
|
||||
vcs_param.vocs_param[i].desc_writable = true;
|
||||
snprintf(output_desc[i], sizeof(output_desc[i]), "Output %d", i + 1);
|
||||
vcs_param.vocs_param[i].output_desc = output_desc[i];
|
||||
vcs_param.vocs_param[i].cb = &vocs_cbs;
|
||||
}
|
||||
|
||||
for (int i = 0; i < ARRAY_SIZE(vcs_param.aics_param); i++) {
|
||||
vcs_param.aics_param[i].desc_writable = true;
|
||||
snprintf(input_desc[i], sizeof(input_desc[i]), "Input %d", i + 1);
|
||||
vcs_param.aics_param[i].description = input_desc[i];
|
||||
vcs_param.aics_param[i].type = BT_AICS_INPUT_TYPE_UNSPECIFIED;
|
||||
vcs_param.aics_param[i].status = true;
|
||||
vcs_param.aics_param[i].gain_mode = BT_AICS_MODE_MANUAL;
|
||||
vcs_param.aics_param[i].units = 1;
|
||||
vcs_param.aics_param[i].min_gain = -100;
|
||||
vcs_param.aics_param[i].max_gain = 100;
|
||||
vcs_param.aics_param[i].cb = &aics_cbs;
|
||||
}
|
||||
|
||||
vcs_param.step = 1;
|
||||
vcs_param.mute = BT_VCS_STATE_UNMUTED;
|
||||
vcs_param.volume = 100;
|
||||
vcs_param.cb = &vcs_cbs;
|
||||
|
||||
err = bt_vcs_register(&vcs_param, &vcs);
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
|
||||
err = bt_vcs_included_get(vcs, &vcs_included);
|
||||
if (err != 0) {
|
||||
return err;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
Loading…
Reference in a new issue