bluetooth: tester: Add VCP & IAS test handling

This commit introduces Volume Control Profile and
Immediate Alert Service into btp.

VCP includes:
Audio Input Control Service
Volume Control Service
Volume Offset Control Service

Signed-off-by: Szymon Czapracki <szymon.czapracki@codecoup.pl>
This commit is contained in:
Szymon Czapracki 2022-12-19 22:06:12 +01:00 committed by Carles Cufí
commit 7941a88121
11 changed files with 827 additions and 1 deletions

View file

@ -9,6 +9,7 @@ project(tester)
zephyr_library_include_directories(${ZEPHYR_BASE}/samples/bluetooth) zephyr_library_include_directories(${ZEPHYR_BASE}/samples/bluetooth)
zephyr_library_include_directories(${ZEPHYR_BASE}/subsys/bluetooth/mesh) zephyr_library_include_directories(${ZEPHYR_BASE}/subsys/bluetooth/mesh)
zephyr_library_include_directories(${ZEPHYR_BASE}/subsys/bluetooth/audio)
zephyr_library_include_directories(${ZEPHYR_BASE}/subsys/bluetooth/host) zephyr_library_include_directories(${ZEPHYR_BASE}/subsys/bluetooth/host)
target_sources(app PRIVATE target_sources(app PRIVATE
src/main.c src/main.c
@ -23,3 +24,9 @@ endif()
if(CONFIG_BT_MESH) if(CONFIG_BT_MESH)
target_sources(app PRIVATE src/btp_mesh.c) target_sources(app PRIVATE src/btp_mesh.c)
endif() endif()
target_sources_ifdef(CONFIG_BT_VCP_VOL_REND app PRIVATE src/btp_vcp.c)
if(CONFIG_BT_IAS OR CONFIG_BT_IAS_CLIENT)
target_sources(app PRIVATE src/btp_ias.c)
endif()

View file

@ -56,5 +56,27 @@ CONFIG_BT_MESH_MSG_CACHE_SIZE=10
CONFIG_BT_TINYCRYPT_ECC=y CONFIG_BT_TINYCRYPT_ECC=y
CONFIG_BT_TESTING=y CONFIG_BT_TESTING=y
CONFIG_UTF8=y
CONFIG_UART_CONSOLE=n CONFIG_UART_CONSOLE=n
# LE Audio Section
CONFIG_BT_AUDIO=y
# Volume Offset Control Service
CONFIG_BT_VOCS_MAX_INSTANCE_COUNT=2
CONFIG_BT_VOCS_CLIENT_MAX_INSTANCE_COUNT=2
# Audio Input Control Service
CONFIG_BT_AICS_MAX_INSTANCE_COUNT=4
CONFIG_BT_AICS_CLIENT_MAX_INSTANCE_COUNT=4
#Volume Control
CONFIG_BT_VCP_VOL_REND=y
CONFIG_BT_VCP_VOL_REND_VOCS_INSTANCE_COUNT=2
CONFIG_BT_VCP_VOL_REND_AICS_INSTANCE_COUNT=2
CONFIG_BT_VCP_VOL_REND_LOG_LEVEL_DBG=y
# IAS
CONFIG_BT_IAS=y
CONFIG_BT_IAS_CLIENT=y

View file

@ -60,7 +60,7 @@ static void supported_commands(uint8_t *data, uint16_t len)
static void supported_services(uint8_t *data, uint16_t len) static void supported_services(uint8_t *data, uint16_t len)
{ {
uint8_t buf[1]; uint8_t buf[2];
struct core_read_supported_services_rp *rp = (void *) buf; struct core_read_supported_services_rp *rp = (void *) buf;
(void)memset(buf, 0, sizeof(buf)); (void)memset(buf, 0, sizeof(buf));
@ -74,6 +74,18 @@ static void supported_services(uint8_t *data, uint16_t len)
#if defined(CONFIG_BT_MESH) #if defined(CONFIG_BT_MESH)
tester_set_bit(buf, BTP_SERVICE_ID_MESH); tester_set_bit(buf, BTP_SERVICE_ID_MESH);
#endif /* CONFIG_BT_MESH */ #endif /* CONFIG_BT_MESH */
#if defined(CONFIG_BT_VCP_VOL_REND)
tester_set_bit(buf, BTP_SERVICE_ID_VCS);
#endif /* CONFIG_BT_VCP_VOL_REND */
#if defined(CONFIG_BT_IAS) || defined(CONFIG_BT_IAS_CLIENT)
tester_set_bit(buf, BTP_SERVICE_ID_IAS);
#endif /* CONFIG_BT_IAS */
#if defined(CONFIG_BT_AICS) || defined(CONFIG_BT_AICS_CLIENT)
tester_set_bit(buf, BTP_SERVICE_ID_AICS);
#endif /*CONFIG_BT_AICS */
#if defined(CONFIG_BT_VOCS) || defined(CONFIG_BT_VOCS_CLIENT)
tester_set_bit(buf, BTP_SERVICE_ID_VOCS);
#endif /* CONFIG_BT_VOCS */
tester_send(BTP_SERVICE_ID_CORE, CORE_READ_SUPPORTED_SERVICES, tester_send(BTP_SERVICE_ID_CORE, CORE_READ_SUPPORTED_SERVICES,
BTP_INDEX_NONE, (uint8_t *) rp, sizeof(buf)); BTP_INDEX_NONE, (uint8_t *) rp, sizeof(buf));
@ -105,6 +117,26 @@ static void register_service(uint8_t *data, uint16_t len)
status = tester_init_mesh(); status = tester_init_mesh();
break; break;
#endif /* CONFIG_BT_MESH */ #endif /* CONFIG_BT_MESH */
#if defined(CONFIG_BT_VCP_VOL_REND)
case BTP_SERVICE_ID_VCS:
status = tester_init_vcp();
break;
#endif /* CONFIG_BT_VCP_VOL_REND */
#if defined(CONFIG_BT_VOCS) || defined(CONFIG_BT_VOCS_CLIENT)
case BTP_SERVICE_ID_VOCS:
status = tester_init_vcp();
break;
#endif /* CONFIG_BT_VOCS */
#if defined(CONFIG_BT_AICS) || defined(CONFIG_BT_AICS_CLIENT)
case BTP_SERVICE_ID_AICS:
status = tester_init_vcp();
break;
#endif /* CONFIG_BT_AICS */
#if defined(CONFIG_BT_IAS) || defined(CONFIG_BT_IAS_CLIENT)
case BTP_SERVICE_ID_IAS:
status = BTP_STATUS_SUCCESS;
break;
#endif /* CONFIG_BT_IAS */
default: default:
LOG_WRN("unknown id: 0x%02x", cmd->id); LOG_WRN("unknown id: 0x%02x", cmd->id);
status = BTP_STATUS_FAILED; status = BTP_STATUS_FAILED;
@ -138,6 +170,21 @@ static void unregister_service(uint8_t *data, uint16_t len)
status = tester_unregister_mesh(); status = tester_unregister_mesh();
break; break;
#endif /* CONFIG_BT_MESH */ #endif /* CONFIG_BT_MESH */
#if defined(CONFIG_BT_VCP_VOL_REND)
case BTP_SERVICE_ID_VCS:
status = tester_unregister_vcp();
break;
#endif /* CONFIG_BT_VCP_VOL_REND */
#if defined(CONFIG_BT_AICS) || defined(CONFIG_BT_AICS_CLIENT)
case BTP_SERVICE_ID_AICS:
status = tester_unregister_vcp();
break;
#endif /* CONFIG_BT_AICS */
#if defined(CONFIG_BT_VOCS) || defined(CONFIG_BT_VOCS_CLIENT)
case BTP_SERVICE_ID_VOCS:
status = tester_unregister_vcp();
break;
#endif /* CONFIG_BT_VOCS */
default: default:
LOG_WRN("unknown id: 0x%x", cmd->id); LOG_WRN("unknown id: 0x%x", cmd->id);
status = BTP_STATUS_FAILED; status = BTP_STATUS_FAILED;
@ -217,6 +264,24 @@ static void cmd_handler(void *p1, void *p2, void *p3)
cmd->hdr.data, len); cmd->hdr.data, len);
break; break;
#endif /* CONFIG_BT_MESH */ #endif /* CONFIG_BT_MESH */
#if defined(CONFIG_BT_VCP_VOL_REND)
case BTP_SERVICE_ID_VCS:
tester_handle_vcs(cmd->hdr.opcode, cmd->hdr.index,
cmd->hdr.data, len);
break;
#endif /* CONFIG_BT_VCP_VOL_REND */
#if defined(CONFIG_BT_AICS)
case BTP_SERVICE_ID_AICS:
tester_handle_aics(cmd->hdr.opcode, cmd->hdr.index,
cmd->hdr.data, len);
break;
#endif /* CONFIG_BT_AICS */
#if defined(CONFIG_BT_VOCS)
case BTP_SERVICE_ID_VOCS:
tester_handle_vocs(cmd->hdr.opcode, cmd->hdr.index,
cmd->hdr.data, len);
break;
#endif /* CONFIG_BT_VOCS */
default: default:
LOG_WRN("unknown service: 0x%x", cmd->hdr.service); LOG_WRN("unknown service: 0x%x", cmd->hdr.service);
tester_rsp(cmd->hdr.service, cmd->hdr.opcode, tester_rsp(cmd->hdr.service, cmd->hdr.opcode,

View file

@ -16,6 +16,10 @@
#include "btp_gatt.h" #include "btp_gatt.h"
#include "btp_l2cap.h" #include "btp_l2cap.h"
#include "btp_mesh.h" #include "btp_mesh.h"
#include "btp_vcs.h"
#include "btp_aics.h"
#include "btp_vocs.h"
#include "btp_ias.h"
#define BTP_MTU 1024 #define BTP_MTU 1024
#define BTP_DATA_MAX_SIZE (BTP_MTU - sizeof(struct btp_hdr)) #define BTP_DATA_MAX_SIZE (BTP_MTU - sizeof(struct btp_hdr))
@ -27,6 +31,13 @@
#define BTP_SERVICE_ID_GATT 2 #define BTP_SERVICE_ID_GATT 2
#define BTP_SERVICE_ID_L2CAP 3 #define BTP_SERVICE_ID_L2CAP 3
#define BTP_SERVICE_ID_MESH 4 #define BTP_SERVICE_ID_MESH 4
#define BTP_SERVICE_ID_MESH_MDL 5
#define BTP_SERVICE_GATT_CLIENT 6
#define BTP_SERVICE_GATT_SERVER 7
#define BTP_SERVICE_ID_VCS 8
#define BTP_SERVICE_ID_IAS 9
#define BTP_SERVICE_ID_AICS 10
#define BTP_SERVICE_ID_VOCS 11
#define BTP_STATUS_SUCCESS 0x00 #define BTP_STATUS_SUCCESS 0x00
#define BTP_STATUS_FAILED 0x01 #define BTP_STATUS_FAILED 0x01

View file

@ -0,0 +1,30 @@
/* btp_aics.h - Bluetooth tester headers */
/*
* Copyright (c) 2022 Codecoup
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/sys/util.h>
#define AICS_READ_SUPPORTED_COMMANDS 0x01
#define AICS_SET_GAIN 0x02
#define AICS_MUTE 0x03
#define AICS_UNMUTE 0x04
#define AICS_MAN_GAIN 0x05
#define AICS_AUTO_GAIN 0x06
#define AICS_MAN_GAIN_ONLY 0x07
#define AICS_AUTO_GAIN_ONLY 0x08
#define AICS_DESCRIPTION 0x09
#define AICS_MUTE_DISABLE 0x0a
struct aics_set_gain {
int8_t gain;
} __packed;
struct aics_audio_desc {
uint8_t desc_len;
uint8_t desc[0];
} __packed;

View file

@ -0,0 +1,16 @@
/* btp_ias.h - Bluetooth tester headers */
/*
* Copyright (c) 2022 Codecoup
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "zephyr/bluetooth/services/ias.h"
#include <stdint.h>
/* events */
#define IAS_EV_OUT_ALERT_ACTION 0x80
struct ias_alert_action_ev {
uint8_t alert_lvl;
} __packed;

View file

@ -0,0 +1,21 @@
/* btp_vcs.h - Bluetooth tester headers */
/*
* Copyright (c) 2022 Codecoup
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/sys/util.h>
#define VCS_READ_SUPPORTED_COMMANDS 0x01
#define VCS_SET_VOL 0x02
#define VCS_VOL_UP 0x03
#define VCS_VOL_DOWN 0x04
#define VCS_MUTE 0x05
#define VCS_UNMUTE 0x06
struct vcs_set_vol_cmd {
uint8_t volume;
} __packed;

View file

@ -0,0 +1,23 @@
/* btp_vocs.h - Bluetooth tester headers */
/*
* Copyright (c) 2022 Codecoup
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/sys/util.h>
#define VOCS_READ_SUPPORTED_COMMANDS 0x01
#define VOCS_UPDATE_LOC 0x02
#define VOCS_UPDATE_DESC 0x03
struct vocs_audio_desc {
uint8_t desc_len;
uint8_t desc[0];
} __packed;
struct vocs_audio_loc {
uint32_t loc;
} __packed;

View file

@ -7,6 +7,7 @@
* SPDX-License-Identifier: Apache-2.0 * SPDX-License-Identifier: Apache-2.0
*/ */
#include <zephyr/types.h>
#include <zephyr/sys/util.h> #include <zephyr/sys/util.h>
#include <zephyr/bluetooth/addr.h> #include <zephyr/bluetooth/addr.h>
@ -43,6 +44,12 @@ uint8_t tester_init_mesh(void);
uint8_t tester_unregister_mesh(void); uint8_t tester_unregister_mesh(void);
void tester_handle_mesh(uint8_t opcode, uint8_t index, uint8_t *data, uint16_t len); void tester_handle_mesh(uint8_t opcode, uint8_t index, uint8_t *data, uint16_t len);
uint8_t tester_init_vcp(void);
uint8_t tester_unregister_vcp(void);
void tester_handle_vcs(uint8_t opcode, uint8_t index, uint8_t *data, uint16_t len);
void tester_handle_aics(uint8_t opcode, uint8_t index, uint8_t *data, uint16_t len);
void tester_handle_vocs(uint8_t opcode, uint8_t index, uint8_t *data, uint16_t len);
uint8_t tester_init_gap(void); uint8_t tester_init_gap(void);
uint8_t tester_unregister_gap(void); uint8_t tester_unregister_gap(void);
void tester_handle_gap(uint8_t opcode, uint8_t index, uint8_t *data, void tester_handle_gap(uint8_t opcode, uint8_t index, uint8_t *data,

View file

@ -0,0 +1,55 @@
/* btp_ias.c - Bluetooth IAS Server Tester */
/*
* Copyright (c) 2022 Codecoup
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/bluetooth/services/ias.h>
#include "btp/btp.h"
#include "zephyr/sys/byteorder.h"
#include <stdint.h>
#include <zephyr/logging/log.h>
#define LOG_MODULE_NAME bttester_ias
LOG_MODULE_REGISTER(LOG_MODULE_NAME);
#define CONTROLLER_INDEX 0
/* Immediate Alert Service */
static void alert_stop(void)
{
struct ias_alert_action_ev ev;
ev.alert_lvl = BT_IAS_ALERT_LVL_NO_ALERT;
tester_send(BTP_SERVICE_ID_IAS, IAS_EV_OUT_ALERT_ACTION,
CONTROLLER_INDEX, (uint8_t *)&ev, sizeof(ev));
}
static void alert_start(void)
{
struct ias_alert_action_ev ev;
ev.alert_lvl = BT_IAS_ALERT_LVL_MILD_ALERT;
tester_send(BTP_SERVICE_ID_IAS, IAS_EV_OUT_ALERT_ACTION,
CONTROLLER_INDEX, (uint8_t *)&ev.alert_lvl, sizeof(ev));
}
static void alert_high_start(void)
{
struct ias_alert_action_ev ev;
ev.alert_lvl = BT_IAS_ALERT_LVL_HIGH_ALERT;
tester_send(BTP_SERVICE_ID_IAS, IAS_EV_OUT_ALERT_ACTION,
CONTROLLER_INDEX, (uint8_t *)&ev, sizeof(ev));
}
BT_IAS_CB_DEFINE(ias_callbacks) = {
.no_alert = alert_stop,
.mild_alert = alert_start,
.high_alert = alert_high_start,
};

View file

@ -0,0 +1,569 @@
/* btp_vcp.c - Bluetooth VCP Tester */
/*
* Copyright (c) 2022 Codecoup
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdio.h>
#include <zephyr/sys/byteorder.h>
#include <zephyr/bluetooth/bluetooth.h>
#include <zephyr/bluetooth/testing.h>
#include <zephyr/bluetooth/audio/vcp.h>
#include <zephyr/bluetooth/audio/aics.h>
#include "zephyr/bluetooth/audio/vocs.h"
#include "zephyr/sys/util.h"
#include <app_keys.h>
#include <zephyr/logging/log.h>
#define LOG_MODULE_NAME bttester_vcp
LOG_MODULE_REGISTER(LOG_MODULE_NAME);
#include "btp/btp.h"
#define CONTROLLER_INDEX 0
#define BT_AICS_MAX_INPUT_DESCRIPTION_SIZE 16
#define BT_AICS_MAX_OUTPUT_DESCRIPTION_SIZE 16
struct bt_vcp_vol_rend_register_param vcp_register_param;
struct bt_vcp_included included;
static void set_register_params(uint8_t gain_mode);
uint8_t tester_init_vcp(void);
/* Volume Control Service */
static void vcs_supported_commands(uint8_t *data, uint16_t len)
{
struct net_buf_simple *buf = NET_BUF_SIMPLE(BTP_DATA_MAX_SIZE);
net_buf_simple_init(buf, 0);
net_buf_simple_add_u8(buf, VCS_READ_SUPPORTED_COMMANDS);
net_buf_simple_add_u8(buf, VCS_SET_VOL);
net_buf_simple_add_u8(buf, VCS_VOL_UP);
net_buf_simple_add_u8(buf, VCS_VOL_DOWN);
net_buf_simple_add_u8(buf, VCS_MUTE);
net_buf_simple_add_u8(buf, VCS_UNMUTE);
tester_send(BTP_SERVICE_ID_VCS, VCS_READ_SUPPORTED_COMMANDS,
CONTROLLER_INDEX, buf->data, buf->len);
}
static void set_volume(uint8_t *data)
{
const struct vcs_set_vol_cmd *cmd = (void *)data;
uint8_t volume;
volume = cmd->volume;
LOG_DBG("Set volume 0x%02x", volume);
if (bt_vcp_vol_rend_set_vol(volume) != 0) {
tester_rsp(BTP_SERVICE_ID_VCS, VCS_SET_VOL,
CONTROLLER_INDEX, BTP_STATUS_FAILED);
return;
}
tester_rsp(BTP_SERVICE_ID_VCS, VCS_SET_VOL, CONTROLLER_INDEX,
BTP_STATUS_SUCCESS);
}
static void vol_up(void)
{
LOG_DBG("Volume Up");
if (bt_vcp_vol_rend_vol_up() != 0) {
tester_rsp(BTP_SERVICE_ID_VCS, VCS_VOL_UP,
CONTROLLER_INDEX, BTP_STATUS_FAILED);
return;
}
tester_rsp(BTP_SERVICE_ID_VCS, VCS_VOL_UP, CONTROLLER_INDEX,
BTP_STATUS_SUCCESS);
}
static void vol_down(void)
{
LOG_DBG("Volume Down");
if (bt_vcp_vol_rend_vol_down() != 0) {
tester_rsp(BTP_SERVICE_ID_VCS, VCS_VOL_DOWN,
CONTROLLER_INDEX, BTP_STATUS_FAILED);
return;
}
tester_rsp(BTP_SERVICE_ID_VCS, VCS_VOL_DOWN, CONTROLLER_INDEX,
BTP_STATUS_SUCCESS);
}
static void mute(void)
{
LOG_DBG("Mute");
if (bt_vcp_vol_rend_mute() != 0) {
tester_rsp(BTP_SERVICE_ID_VCS, VCS_MUTE, CONTROLLER_INDEX,
BTP_STATUS_FAILED);
return;
}
tester_rsp(BTP_SERVICE_ID_VCS, VCS_MUTE, CONTROLLER_INDEX,
BTP_STATUS_SUCCESS);
}
static void unmute(void)
{
LOG_DBG("Unmute");
if (bt_vcp_vol_rend_unmute() != 0) {
tester_rsp(BTP_SERVICE_ID_VCS, VCS_UNMUTE, CONTROLLER_INDEX,
BTP_STATUS_FAILED);
return;
}
tester_rsp(BTP_SERVICE_ID_VCS, VCS_UNMUTE, CONTROLLER_INDEX,
BTP_STATUS_SUCCESS);
}
static void vcs_state_cb(int err, uint8_t volume, uint8_t mute)
{
LOG_DBG("VCP state cb err (%d)", err);
}
static void vcs_flags_cb(int err, uint8_t flags)
{
LOG_DBG("VCP flags cb err (%d)", err);
}
static struct bt_vcp_vol_rend_cb vcs_cb = {
.state = vcs_state_cb,
.flags = vcs_flags_cb,
};
void tester_handle_vcs(uint8_t opcode, uint8_t index, uint8_t *data,
uint16_t len)
{
switch (opcode) {
case VCS_READ_SUPPORTED_COMMANDS:
vcs_supported_commands(data, len);
break;
case VCS_SET_VOL:
set_volume(data);
break;
case VCS_VOL_UP:
vol_up();
break;
case VCS_VOL_DOWN:
vol_down();
break;
case VCS_MUTE:
mute();
break;
case VCS_UNMUTE:
unmute();
break;
default:
tester_rsp(BTP_SERVICE_ID_VCS, opcode, index,
BTP_STATUS_UNKNOWN_CMD);
break;
}
}
/* Audio Input Control Service */
static void aics_supported_commands(uint8_t *data, uint16_t len)
{
struct net_buf_simple *buf = NET_BUF_SIMPLE(BTP_DATA_MAX_SIZE);
net_buf_simple_init(buf, 0);
net_buf_simple_add_u8(buf, AICS_READ_SUPPORTED_COMMANDS);
net_buf_simple_add_u8(buf, AICS_SET_GAIN);
net_buf_simple_add_u8(buf, AICS_MUTE);
net_buf_simple_add_u8(buf, AICS_UNMUTE);
net_buf_simple_add_u8(buf, AICS_MAN_GAIN);
net_buf_simple_add_u8(buf, AICS_AUTO_GAIN);
net_buf_simple_add_u8(buf, AICS_DESCRIPTION);
tester_send(BTP_SERVICE_ID_AICS, AICS_READ_SUPPORTED_COMMANDS,
CONTROLLER_INDEX, buf->data, buf->len);
}
static void aics_state_cb(struct bt_aics *inst, int err, int8_t gain,
uint8_t mute, uint8_t mode)
{
LOG_DBG("AICS state callback (%d)", err);
}
static void aics_gain_setting_cb(struct bt_aics *inst, int err, uint8_t units,
int8_t minimum, int8_t maximum)
{
LOG_DBG("AICS gain setting callback (%d)", err);
}
static void aics_input_type_cb(struct bt_aics *inst, int err,
uint8_t input_type)
{
LOG_DBG("AICS input type callback (%d)", err);
}
static void aics_status_cb(struct bt_aics *inst, int err, bool active)
{
LOG_DBG("AICS status callback (%d)", err);
}
static void aics_description_cb(struct bt_aics *inst, int err,
char *description)
{
LOG_DBG("AICS description callback (%d)", err);
}
static struct bt_aics_cb aics_cb = {
.state = aics_state_cb,
.gain_setting = aics_gain_setting_cb,
.type = aics_input_type_cb,
.status = aics_status_cb,
.description = aics_description_cb
};
void aics_set_gain(uint8_t *data)
{
const struct aics_set_gain *cmd = (void *)data;
const int8_t gain = cmd->gain;
LOG_DBG("AICS set gain %d", gain);
for (int i = 0; i < CONFIG_BT_VCP_VOL_REND_AICS_INSTANCE_COUNT; i++) {
if (bt_aics_gain_set(included.aics[0], gain) != 0) {
tester_rsp(BTP_SERVICE_ID_AICS, AICS_SET_GAIN,
CONTROLLER_INDEX, BTP_STATUS_FAILED);
return;
}
}
tester_rsp(BTP_SERVICE_ID_AICS, AICS_SET_GAIN, CONTROLLER_INDEX,
BTP_STATUS_SUCCESS);
}
void aics_mute(void)
{
LOG_DBG("AICS mute");
for (int i = 0; i < CONFIG_BT_VCP_VOL_REND_AICS_INSTANCE_COUNT; i++) {
if (bt_aics_mute(included.aics[i]) != 0) {
tester_rsp(BTP_SERVICE_ID_AICS, AICS_MUTE,
CONTROLLER_INDEX, BTP_STATUS_FAILED);
return;
}
}
tester_rsp(BTP_SERVICE_ID_AICS, AICS_MUTE, CONTROLLER_INDEX,
BTP_STATUS_SUCCESS);
}
void aics_unmute(void)
{
LOG_DBG("AICS unmute");
for (int i = 0; i < CONFIG_BT_VCP_VOL_REND_AICS_INSTANCE_COUNT; i++) {
if (bt_aics_unmute(included.aics[i]) != 0) {
tester_rsp(BTP_SERVICE_ID_AICS, AICS_UNMUTE,
CONTROLLER_INDEX, BTP_STATUS_FAILED);
return;
}
}
tester_rsp(BTP_SERVICE_ID_AICS, AICS_UNMUTE, CONTROLLER_INDEX,
BTP_STATUS_SUCCESS);
}
void aics_man_gain(void)
{
LOG_DBG("AICS manual gain set");
for (int i = 0; i < CONFIG_BT_VCP_VOL_REND_AICS_INSTANCE_COUNT; i++) {
if (bt_aics_manual_gain_set(included.aics[i]) != 0) {
tester_rsp(BTP_SERVICE_ID_AICS, AICS_MAN_GAIN,
CONTROLLER_INDEX, BTP_STATUS_FAILED);
return;
}
}
tester_rsp(BTP_SERVICE_ID_AICS, AICS_MAN_GAIN, CONTROLLER_INDEX,
BTP_STATUS_SUCCESS);
}
void aics_auto_gain(void)
{
LOG_DBG("AICS auto gain set");
for (int i = 0; i < CONFIG_BT_VCP_VOL_REND_AICS_INSTANCE_COUNT; i++) {
if (bt_aics_automatic_gain_set(included.aics[i]) != 0) {
tester_rsp(BTP_SERVICE_ID_AICS, AICS_AUTO_GAIN,
CONTROLLER_INDEX, BTP_STATUS_FAILED);
return;
}
}
tester_rsp(BTP_SERVICE_ID_AICS, AICS_AUTO_GAIN, CONTROLLER_INDEX,
BTP_STATUS_SUCCESS);
}
/* TODO: Need new API function for this */
void aics_auto_gain_only(void)
{
LOG_DBG("AICS auto gain only");
tester_rsp(BTP_SERVICE_ID_AICS, AICS_AUTO_GAIN_ONLY, CONTROLLER_INDEX,
BTP_STATUS_FAILED);
}
/* TODO: Need new API function for this */
void aics_auto_man_only(void)
{
LOG_DBG("AICS manual gain only");
tester_rsp(BTP_SERVICE_ID_AICS, AICS_MAN_GAIN_ONLY, CONTROLLER_INDEX,
BTP_STATUS_FAILED);
}
/* TODO: Need new API function for this */
void aics_mute_disable(void)
{
LOG_DBG("AICS mute disable");
tester_rsp(BTP_SERVICE_ID_AICS, AICS_MUTE_DISABLE,
CONTROLLER_INDEX, BTP_STATUS_FAILED);
}
void aics_desc(uint8_t *data)
{
const struct aics_audio_desc *cmd = (void *) data;
char description[BT_AICS_MAX_INPUT_DESCRIPTION_SIZE];
LOG_DBG("AICS description");
if (cmd->desc_len > BT_AICS_MAX_INPUT_DESCRIPTION_SIZE) {
LOG_ERR("Too long input (%u chars required)",
BT_AICS_MAX_INPUT_DESCRIPTION_SIZE);
goto rsp;
}
memcpy(description, cmd->desc, cmd->desc_len);
description[cmd->desc_len] = '\0';
for (int i = 0; i < CONFIG_BT_VCP_VOL_REND_AICS_INSTANCE_COUNT; i++) {
if (bt_aics_description_set(included.aics[i], description) != 0) {
tester_rsp(BTP_SERVICE_ID_AICS, AICS_DESCRIPTION,
CONTROLLER_INDEX, BTP_STATUS_FAILED);
return;
}
}
rsp:
tester_rsp(BTP_SERVICE_ID_AICS, AICS_DESCRIPTION, CONTROLLER_INDEX,
BTP_STATUS_FAILED);
}
void tester_handle_aics(uint8_t opcode, uint8_t index, uint8_t *data, uint16_t len)
{
switch (opcode) {
case AICS_READ_SUPPORTED_COMMANDS:
aics_supported_commands(data, len);
break;
case AICS_SET_GAIN:
aics_set_gain(data);
break;
case AICS_MUTE:
aics_mute();
break;
case AICS_UNMUTE:
aics_unmute();
break;
case AICS_MAN_GAIN:
aics_man_gain();
break;
case AICS_AUTO_GAIN:
aics_auto_gain();
break;
case AICS_MAN_GAIN_ONLY:
aics_auto_gain_only();
break;
case AICS_AUTO_GAIN_ONLY:
aics_auto_gain_only();
break;
case AICS_DESCRIPTION:
aics_desc(data);
break;
case AICS_MUTE_DISABLE:
aics_mute_disable();
break;
default:
tester_rsp(BTP_SERVICE_ID_AICS, opcode, index,
BTP_STATUS_UNKNOWN_CMD);
break;
}
}
/* Volume Offset Control Service */
static void vocs_supported_commands(void)
{
struct net_buf_simple *buf = NET_BUF_SIMPLE(BTP_DATA_MAX_SIZE);
net_buf_simple_init(buf, 0);
net_buf_simple_add_u8(buf, VOCS_READ_SUPPORTED_COMMANDS);
net_buf_simple_add_u8(buf, VOCS_UPDATE_LOC);
net_buf_simple_add_u8(buf, VOCS_UPDATE_DESC);
tester_send(BTP_SERVICE_ID_VOCS, VOCS_READ_SUPPORTED_COMMANDS,
CONTROLLER_INDEX, buf->data, buf->len);
}
static void vocs_state_cb(struct bt_vocs *inst, int err, int16_t offset)
{
LOG_DBG("VOCS state callback err (%d)", err);
}
static void vocs_location_cb(struct bt_vocs *inst, int err, uint32_t location)
{
LOG_DBG("VOCS location callback err (%d)", err);
}
static void vocs_description_cb(struct bt_vocs *inst, int err,
char *description)
{
LOG_DBG("VOCS desctripion callback (%d)", err);
}
static struct bt_vocs_cb vocs_cb = {
.state = vocs_state_cb,
.location = vocs_location_cb,
.description = vocs_description_cb
};
void vocs_audio_desc(uint8_t *data)
{
struct vocs_audio_desc *cmd = (void *) data;
char description[BT_AICS_MAX_OUTPUT_DESCRIPTION_SIZE];
LOG_DBG("VOCS description");
/* FIXME: Spec does not specify maximum desc length */
if (cmd->desc_len >= BT_AICS_MAX_OUTPUT_DESCRIPTION_SIZE) {
LOG_ERR("Too long input (%u chars required)", 16);
goto rsp;
}
memcpy(description, cmd->desc, cmd->desc_len);
description[cmd->desc_len] = '\0';
for (int i = 0; i < CONFIG_BT_VCP_VOL_REND_VOCS_INSTANCE_COUNT; i++) {
if (bt_vocs_description_set(included.vocs[i], description) != 0) {
tester_rsp(BTP_SERVICE_ID_VOCS, VOCS_UPDATE_DESC,
CONTROLLER_INDEX, BTP_STATUS_FAILED);
return;
}
}
rsp:
tester_rsp(BTP_SERVICE_ID_VOCS, VOCS_UPDATE_DESC, CONTROLLER_INDEX,
BTP_STATUS_SUCCESS);
}
void vocs_audio_loc(uint8_t *data)
{
const struct vocs_audio_loc *cmd = (void *) data;
LOG_DBG("VOCS location");
for (int i = 0; i < CONFIG_BT_VCP_VOL_REND_VOCS_INSTANCE_COUNT; i++) {
if (bt_vocs_location_set(included.vocs[i], cmd->loc) != 0) {
tester_rsp(BTP_SERVICE_ID_VOCS, VOCS_UPDATE_LOC,
CONTROLLER_INDEX, BTP_STATUS_FAILED);
return;
}
}
tester_rsp(BTP_SERVICE_ID_VOCS, VOCS_UPDATE_LOC, CONTROLLER_INDEX,
BTP_STATUS_SUCCESS);
}
void tester_handle_vocs(uint8_t opcode, uint8_t index, uint8_t *data,
uint16_t len)
{
switch (opcode) {
case VOCS_READ_SUPPORTED_COMMANDS:
vocs_supported_commands();
break;
case VOCS_UPDATE_DESC:
vocs_audio_desc(data);
break;
case VOCS_UPDATE_LOC:
vocs_audio_loc(data);
break;
default:
tester_rsp(BTP_SERVICE_ID_VOCS, opcode, index,
BTP_STATUS_UNKNOWN_CMD);
break;
}
}
/* General profile handling */
static void set_register_params(uint8_t gain_mode)
{
char input_desc[CONFIG_BT_VCP_VOL_REND_AICS_INSTANCE_COUNT]
[BT_AICS_MAX_INPUT_DESCRIPTION_SIZE];
char output_desc[CONFIG_BT_VCP_VOL_REND_VOCS_INSTANCE_COUNT]
[BT_AICS_MAX_OUTPUT_DESCRIPTION_SIZE];
memset(&vcp_register_param, 0, sizeof(vcp_register_param));
for (size_t i = 0; i < ARRAY_SIZE(vcp_register_param.vocs_param); i++) {
vcp_register_param.vocs_param[i].location_writable = true;
vcp_register_param.vocs_param[i].desc_writable = true;
snprintf(output_desc[i], sizeof(output_desc[i]),
"Output %zu", i + 1);
vcp_register_param.vocs_param[i].output_desc = output_desc[i];
vcp_register_param.vocs_param[i].cb = &vocs_cb;
}
for (size_t i = 0; i < ARRAY_SIZE(vcp_register_param.aics_param); i++) {
vcp_register_param.aics_param[i].desc_writable = true;
snprintf(input_desc[i], sizeof(input_desc[i]),
"Input %zu", i + 1);
vcp_register_param.aics_param[i].description = input_desc[i];
vcp_register_param.aics_param[i].type = BT_AICS_INPUT_TYPE_DIGITAL;
vcp_register_param.aics_param[i].status = 1;
vcp_register_param.aics_param[i].gain_mode = gain_mode;
vcp_register_param.aics_param[i].units = 1;
vcp_register_param.aics_param[i].min_gain = 0;
vcp_register_param.aics_param[i].max_gain = 100;
vcp_register_param.aics_param[i].cb = &aics_cb;
}
vcp_register_param.step = 1;
vcp_register_param.mute = BT_VCP_STATE_UNMUTED;
vcp_register_param.volume = 100;
vcp_register_param.cb = &vcs_cb;
}
uint8_t tester_init_vcp(void)
{
int err;
set_register_params(BT_AICS_MODE_MANUAL);
err = bt_vcp_vol_rend_register(&vcp_register_param);
if (err) {
return BTP_STATUS_FAILED;
}
err = bt_vcp_vol_rend_included_get(&included);
if (err) {
return BTP_STATUS_FAILED;
}
return BTP_STATUS_SUCCESS;
}
uint8_t tester_unregister_vcp(void)
{
return BTP_STATUS_SUCCESS;
}