blob: 1f3bd1b920341cedd1208e128266844c3e2b6add [file] [log] [blame]
/* btp_vcp.c - Bluetooth VCP Tester */
/*
* Copyright (c) 2022 Codecoup
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdio.h>
#include <stdint.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 "btp/btp.h"
#include <../../subsys/bluetooth/audio/micp_internal.h>
#include <../../subsys/bluetooth/audio/aics_internal.h>
#include <../../subsys/bluetooth/audio/vcp_internal.h>
#include <../../subsys/bluetooth/audio/vocs_internal.h>
#include <zephyr/logging/log.h>
#define LOG_MODULE_NAME bttester_vcp
LOG_MODULE_REGISTER(LOG_MODULE_NAME, CONFIG_BTTESTER_LOG_LEVEL);
#define BT_AICS_MAX_INPUT_DESCRIPTION_SIZE 16
#define BT_AICS_MAX_OUTPUT_DESCRIPTION_SIZE 16
static struct bt_vcp_vol_rend_register_param vcp_register_param;
static struct bt_vcp_vol_ctlr *vol_ctlr;
static struct bt_vcp_included included;
extern struct btp_aics_instance aics_server_instance;
extern struct btp_aics_instance aics_client_instance;
extern struct bt_aics_cb aics_client_cb;
struct service_handles {
struct {
uint16_t ctrl_pt;
uint16_t flags;
uint16_t state;
} vcp_handles;
struct {
uint16_t state;
uint16_t location;
uint16_t control;
uint16_t desc;
} vocs_handles;
struct {
uint16_t mute;
uint16_t state;
uint16_t gain;
uint16_t type;
uint16_t status;
uint16_t control;
uint16_t desc;
} aics_handles;
};
struct service_handles chrc_handles;
/* Volume Control Service */
static uint8_t vcs_supported_commands(const void *cmd, uint16_t cmd_len,
void *rsp, uint16_t *rsp_len)
{
struct btp_vcs_read_supported_commands_rp *rp = rsp;
/* octet 0 */
tester_set_bit(rp->data, BTP_VCS_READ_SUPPORTED_COMMANDS);
tester_set_bit(rp->data, BTP_VCS_SET_VOL);
tester_set_bit(rp->data, BTP_VCS_VOL_UP);
tester_set_bit(rp->data, BTP_VCS_VOL_DOWN);
tester_set_bit(rp->data, BTP_VCS_MUTE);
tester_set_bit(rp->data, BTP_VCS_UNMUTE);
*rsp_len = sizeof(*rp) + 1;
return BTP_STATUS_SUCCESS;
}
static uint8_t set_volume(const void *cmd, uint16_t cmd_len,
void *rsp, uint16_t *rsp_len)
{
const struct btp_vcs_set_vol_cmd *cp = cmd;
LOG_DBG("Set volume 0x%02x", cp->volume);
if (bt_vcp_vol_rend_set_vol(cp->volume) != 0) {
return BTP_STATUS_FAILED;
}
return BTP_STATUS_SUCCESS;
}
static uint8_t vol_up(const void *cmd, uint16_t cmd_len,
void *rsp, uint16_t *rsp_len)
{
LOG_DBG("Volume Up");
if (bt_vcp_vol_rend_vol_up() != 0) {
return BTP_STATUS_FAILED;
}
return BTP_STATUS_SUCCESS;
}
static uint8_t vol_down(const void *cmd, uint16_t cmd_len,
void *rsp, uint16_t *rsp_len)
{
LOG_DBG("Volume Down");
if (bt_vcp_vol_rend_vol_down() != 0) {
return BTP_STATUS_FAILED;
}
return BTP_STATUS_SUCCESS;
}
static uint8_t mute(const void *cmd, uint16_t cmd_len,
void *rsp, uint16_t *rsp_len)
{
LOG_DBG("Mute");
if (bt_vcp_vol_rend_mute() != 0) {
return BTP_STATUS_FAILED;
}
return BTP_STATUS_SUCCESS;
}
static uint8_t unmute(const void *cmd, uint16_t cmd_len,
void *rsp, uint16_t *rsp_len)
{
LOG_DBG("Unmute");
if (bt_vcp_vol_rend_unmute() != 0) {
return BTP_STATUS_FAILED;
}
return 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,
};
static const struct btp_handler vcs_handlers[] = {
{
.opcode = BTP_VCS_READ_SUPPORTED_COMMANDS,
.index = BTP_INDEX_NONE,
.expect_len = 0,
.func = vcs_supported_commands,
},
{
.opcode = BTP_VCS_SET_VOL,
.expect_len = sizeof(struct btp_vcs_set_vol_cmd),
.func = set_volume,
},
{
.opcode = BTP_VCS_VOL_UP,
.expect_len = 0,
.func = vol_up,
},
{
.opcode = BTP_VCS_VOL_DOWN,
.expect_len = 0,
.func = vol_down,
},
{
.opcode = BTP_VCS_MUTE,
.expect_len = 0,
.func = mute,
},
{
.opcode = BTP_VCS_UNMUTE,
.expect_len = 0,
.func = unmute,
},
};
/* Volume Offset Control Service */
static uint8_t vocs_supported_commands(const void *cmd, uint16_t cmd_len,
void *rsp, uint16_t *rsp_len)
{
struct btp_vocs_read_supported_commands_rp *rp = rsp;
/* octet 0 */
tester_set_bit(rp->data, BTP_VOCS_READ_SUPPORTED_COMMANDS);
tester_set_bit(rp->data, BTP_VOCS_UPDATE_LOC);
tester_set_bit(rp->data, BTP_VOCS_UPDATE_DESC);
tester_set_bit(rp->data, BTP_VOCS_STATE_GET);
tester_set_bit(rp->data, BTP_VOCS_LOCATION_GET);
tester_set_bit(rp->data, BTP_VOCS_OFFSET_STATE_SET);
*rsp_len = sizeof(*rp) + 1;
return BTP_STATUS_SUCCESS;
}
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
};
static void btp_send_vocs_state_ev(struct bt_conn *conn, uint8_t att_status, int16_t offset)
{
struct btp_vocs_offset_state_ev ev;
bt_addr_le_copy(&ev.address, bt_conn_get_dst(conn));
ev.att_status = att_status;
ev.offset = sys_cpu_to_le16(offset);
tester_event(BTP_SERVICE_ID_VOCS, BTP_VOCS_OFFSET_STATE_EV, &ev, sizeof(ev));
}
static void btp_send_vocs_location_ev(struct bt_conn *conn, uint8_t att_status, uint32_t location)
{
struct btp_vocs_audio_location_ev ev;
bt_addr_le_copy(&ev.address, bt_conn_get_dst(conn));
ev.att_status = att_status;
ev.location = sys_cpu_to_le32(location);
tester_event(BTP_SERVICE_ID_VOCS, BTP_VOCS_AUDIO_LOCATION_EV, &ev, sizeof(ev));
}
static void btp_send_vocs_procedure_ev(struct bt_conn *conn, uint8_t att_status, uint8_t opcode)
{
struct btp_vocs_procedure_ev ev;
bt_addr_le_copy(&ev.address, bt_conn_get_dst(conn));
ev.att_status = att_status;
ev.opcode = opcode;
tester_event(BTP_SERVICE_ID_VOCS, BTP_VOCS_PROCEDURE_EV, &ev, sizeof(ev));
}
static void vcp_vocs_state_cb(struct bt_vocs *inst, int err, int16_t offset)
{
struct bt_conn *conn;
bt_vocs_client_conn_get(inst, &conn);
btp_send_vocs_state_ev(conn, err, offset);
LOG_DBG("VOCS Offset State callback");
}
static void vcp_vocs_location_cb(struct bt_vocs *inst, int err, uint32_t location)
{
struct bt_conn *conn;
bt_vocs_client_conn_get(inst, &conn);
btp_send_vocs_location_ev(conn, err, location);
LOG_DBG("VOCS location callback err (%d)", err);
}
static void vcp_vocs_description_cb(struct bt_vocs *inst, int err,
char *description)
{
LOG_DBG("VOCS desctripion callback (%d)", err);
}
static void vcp_vocs_set_offset_cb(struct bt_vocs *inst, int err)
{
struct bt_conn *conn;
bt_vocs_client_conn_get(inst, &conn);
btp_send_vocs_procedure_ev(conn, err, BTP_VOCS_OFFSET_STATE_SET);
LOG_DBG("VOCS Set Offset callback (%d)", err);
}
static struct bt_vocs_cb vocs_cl_cb = {
.state = vcp_vocs_state_cb,
.location = vcp_vocs_location_cb,
.description = vcp_vocs_description_cb,
#if defined(CONFIG_BT_VOCS_CLIENT)
.set_offset = vcp_vocs_set_offset_cb
#endif /* CONFIG_BT_VOCS_CLIENT */
};
static uint8_t vocs_audio_desc(const void *cmd, uint16_t cmd_len,
void *rsp, uint16_t *rsp_len)
{
const struct btp_vocs_audio_desc_cmd *cp = cmd;
char description[BT_AICS_MAX_OUTPUT_DESCRIPTION_SIZE];
if (cmd_len < sizeof(*cp) ||
cmd_len != sizeof(*cp) + cp->desc_len) {
return BTP_STATUS_FAILED;
}
if (cp->desc_len >= sizeof(description)) {
return BTP_STATUS_FAILED;
}
memcpy(description, cp->desc, cp->desc_len);
description[cp->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) {
return BTP_STATUS_FAILED;
}
}
return BTP_STATUS_SUCCESS;
}
static uint8_t vocs_audio_loc(const void *cmd, uint16_t cmd_len, void *rsp, uint16_t *rsp_len)
{
const struct btp_vocs_audio_loc_cmd *cp = cmd;
uint32_t loc = sys_le32_to_cpu(cp->loc);
for (uint8_t i = 0; i < included.vocs_cnt; i++) {
if (bt_vocs_location_set(included.vocs[i], loc) != 0) {
return BTP_STATUS_FAILED;
}
}
return BTP_STATUS_SUCCESS;
}
static uint8_t vocs_state_get(const void *cmd, uint16_t cmd_len, void *rsp, uint16_t *rsp_len)
{
int err;
LOG_DBG("Volume Offset Control Service offset state get");
err = bt_vocs_state_get(included.vocs[0]);
if (err) {
return BTP_STATUS_FAILED;
}
return BTP_STATUS_SUCCESS;
}
static uint8_t vocs_state_set(const void *cmd, uint16_t cmd_len, void *rsp, uint16_t *rsp_len)
{
const struct btp_vocs_offset_set_cmd *cp = cmd;
int16_t offset = sys_le16_to_cpu(cp->offset);
int err;
LOG_DBG("VCP CTLR Set absolute volume %d", offset);
err = bt_vocs_state_set(included.vocs[0], cp->offset);
if (err) {
return BTP_STATUS_FAILED;
}
return BTP_STATUS_SUCCESS;
}
static uint8_t vocs_audio_location_get(const void *cmd, uint16_t cmd_len, void *rsp,
uint16_t *rsp_len)
{
int err;
LOG_DBG("Volume Offset Control Service Audio Location get");
err = bt_vocs_location_get(included.vocs[0]);
if (err) {
return BTP_STATUS_FAILED;
}
return BTP_STATUS_SUCCESS;
}
static const struct btp_handler vocs_handlers[] = {
{
.opcode = BTP_VOCS_READ_SUPPORTED_COMMANDS,
.index = BTP_INDEX_NONE,
.expect_len = 0,
.func = vocs_supported_commands,
},
{
.opcode = BTP_VOCS_UPDATE_DESC,
.expect_len = BTP_HANDLER_LENGTH_VARIABLE,
.func = vocs_audio_desc,
},
{
.opcode = BTP_VOCS_UPDATE_LOC,
.expect_len = sizeof(struct btp_vocs_audio_loc_cmd),
.func = vocs_audio_loc,
},
{
.opcode = BTP_VOCS_STATE_GET,
.expect_len = sizeof(struct btp_vocs_state_get_cmd),
.func = vocs_state_get,
},
{
.opcode = BTP_VOCS_LOCATION_GET,
.expect_len = sizeof(struct btp_vocs_location_get_cmd),
.func = vocs_audio_location_get,
},
{
.opcode = BTP_VOCS_OFFSET_STATE_SET,
.expect_len = sizeof(struct btp_vocs_offset_set_cmd),
.func = vocs_state_set,
},
};
/* AICS Callbacks */
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);
}
struct bt_aics_cb aics_server_cb = {
.state = aics_state_cb,
.gain_setting = aics_gain_setting_cb,
.type = aics_input_type_cb,
.status = aics_status_cb,
.description = aics_description_cb,
};
/* 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_server_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_vcs(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;
}
aics_server_instance.aics_cnt = included.aics_cnt;
aics_server_instance.aics = included.aics;
tester_register_command_handlers(BTP_SERVICE_ID_VCS, vcs_handlers,
ARRAY_SIZE(vcs_handlers));
return BTP_STATUS_SUCCESS;
}
uint8_t tester_unregister_vcs(void)
{
return BTP_STATUS_SUCCESS;
}
uint8_t tester_init_vocs(void)
{
tester_register_command_handlers(BTP_SERVICE_ID_VOCS, vocs_handlers,
ARRAY_SIZE(vocs_handlers));
return BTP_STATUS_SUCCESS;
}
uint8_t tester_unregister_vocs(void)
{
return BTP_STATUS_SUCCESS;
}
/* Volume Control Profile */
static void btp_send_vcp_found_ev(struct bt_conn *conn, uint8_t att_status,
const struct service_handles *chrc_handles)
{
struct btp_vcp_discovered_ev ev;
bt_addr_le_copy(&ev.address, bt_conn_get_dst(conn));
ev.att_status = att_status;
ev.vcs_handles.control_handle = sys_cpu_to_le16(chrc_handles->vcp_handles.ctrl_pt);
ev.vcs_handles.flag_handle = sys_cpu_to_le16(chrc_handles->vcp_handles.flags);
ev.vcs_handles.state_handle = sys_cpu_to_le16(chrc_handles->vcp_handles.state);
ev.vocs_handles.state_handle = sys_cpu_to_le16(chrc_handles->vocs_handles.state);
ev.vocs_handles.location_handle = sys_cpu_to_le16(chrc_handles->vocs_handles.location);
ev.vocs_handles.control_handle = sys_cpu_to_le16(chrc_handles->vocs_handles.control);
ev.vocs_handles.desc_handle = sys_cpu_to_le16(chrc_handles->vocs_handles.desc);
ev.aics_handles.state_handle = sys_cpu_to_le16(chrc_handles->aics_handles.state);
ev.aics_handles.gain_handle = sys_cpu_to_le16(chrc_handles->aics_handles.gain);
ev.aics_handles.type_handle = sys_cpu_to_le16(chrc_handles->aics_handles.type);
ev.aics_handles.status_handle = sys_cpu_to_le16(chrc_handles->aics_handles.status);
ev.aics_handles.control_handle = sys_cpu_to_le16(chrc_handles->aics_handles.control);
ev.aics_handles.desc_handle = sys_cpu_to_le16(chrc_handles->aics_handles.desc);
tester_event(BTP_SERVICE_ID_VCP, BTP_VCP_DISCOVERED_EV, &ev, sizeof(ev));
}
static void btp_send_vcp_state_ev(struct bt_conn *conn, uint8_t att_status, uint8_t volume,
uint8_t mute)
{
struct btp_vcp_state_ev ev;
bt_addr_le_copy(&ev.address, bt_conn_get_dst(conn));
ev.att_status = att_status;
ev.volume = volume;
ev.mute = mute;
tester_event(BTP_SERVICE_ID_VCP, BTP_VCP_STATE_EV, &ev, sizeof(ev));
}
static void btp_send_vcp_volume_flags_ev(struct bt_conn *conn, uint8_t att_status, uint8_t flags)
{
struct btp_vcp_volume_flags_ev ev;
bt_addr_le_copy(&ev.address, bt_conn_get_dst(conn));
ev.att_status = att_status;
ev.flags = flags;
tester_event(BTP_SERVICE_ID_VCP, BTP_VCP_FLAGS_EV, &ev, sizeof(ev));
}
static void btp_send_vcp_procedure_ev(struct bt_conn *conn, uint8_t att_status, uint8_t opcode)
{
struct btp_vcp_procedure_ev ev;
bt_addr_le_copy(&ev.address, bt_conn_get_dst(conn));
ev.att_status = att_status;
ev.opcode = opcode;
tester_event(BTP_SERVICE_ID_VCP, BTP_VCP_PROCEDURE_EV, &ev, sizeof(ev));
}
static uint8_t vcp_supported_commands(const void *cmd, uint16_t cmd_len,
void *rsp, uint16_t *rsp_len)
{
struct btp_vcp_read_supported_commands_rp *rp = rsp;
/* octet 0 */
tester_set_bit(rp->data, BTP_VCP_READ_SUPPORTED_COMMANDS);
tester_set_bit(rp->data, BTP_VCP_VOL_CTLR_DISCOVER);
tester_set_bit(rp->data, BTP_VCP_VOL_CTLR_STATE_READ);
tester_set_bit(rp->data, BTP_VCP_VOL_CTLR_FLAGS_READ);
tester_set_bit(rp->data, BTP_VCP_VOL_CTLR_VOL_DOWN);
tester_set_bit(rp->data, BTP_VCP_VOL_CTLR_VOL_UP);
tester_set_bit(rp->data, BTP_VCP_VOL_CTLR_UNMUTE_VOL_DOWN);
/* octet 1 */
tester_set_bit(rp->data, BTP_VCP_VOL_CTLR_UNMUTE_VOL_UP);
tester_set_bit(rp->data, BTP_VCP_VOL_CTLR_SET_VOL);
tester_set_bit(rp->data, BTP_VCP_VOL_CTLR_UNMUTE);
tester_set_bit(rp->data, BTP_VCP_VOL_CTLR_MUTE);
*rsp_len = sizeof(*rp) + 1;
return BTP_STATUS_SUCCESS;
}
static void vcp_vol_ctlr_discover_cb(struct bt_vcp_vol_ctlr *vol_ctlr, int err, uint8_t vocs_count,
uint8_t aics_count)
{
struct bt_conn *conn;
if (err) {
LOG_DBG("Discovery failed (%d)", err);
return;
}
LOG_DBG("Discovery done with %u VOCS, %u AICS",
vocs_count, aics_count);
bt_vcp_vol_ctlr_conn_get(vol_ctlr, &conn);
if (bt_vcp_vol_ctlr_included_get(vol_ctlr, &included) != 0) {
LOG_DBG("Could not get included services");
memset(&chrc_handles.vocs_handles, 0, sizeof(chrc_handles.vocs_handles));
memset(&chrc_handles.aics_handles, 0, sizeof(chrc_handles.aics_handles));
} else {
aics_client_instance.aics_cnt = included.aics_cnt;
aics_client_instance.aics = included.aics;
bt_vocs_client_cb_register(vol_ctlr->vocs[0], &vocs_cl_cb);
bt_aics_client_cb_register(vol_ctlr->aics[0], &aics_client_cb);
struct bt_vocs_client *vocs_cli =
CONTAINER_OF(vol_ctlr->vocs[0], struct bt_vocs_client, vocs);
struct bt_aics_client *aics_cli = &vol_ctlr->aics[0]->cli;
chrc_handles.vocs_handles.state = vocs_cli->state_handle;
chrc_handles.vocs_handles.location = vocs_cli->location_handle;
chrc_handles.vocs_handles.control = vocs_cli->control_handle;
chrc_handles.vocs_handles.desc = vocs_cli->desc_handle;
chrc_handles.aics_handles.state = aics_cli->state_handle;
chrc_handles.aics_handles.gain = aics_cli->gain_handle;
chrc_handles.aics_handles.type = aics_cli->type_handle;
chrc_handles.aics_handles.status = aics_cli->status_handle;
chrc_handles.aics_handles.control = aics_cli->control_handle;
chrc_handles.aics_handles.desc = aics_cli->desc_handle;
}
chrc_handles.vcp_handles.ctrl_pt = vol_ctlr->control_handle;
chrc_handles.vcp_handles.flags = vol_ctlr->flag_handle;
chrc_handles.vcp_handles.state = vol_ctlr->state_handle;
btp_send_vcp_found_ev(conn, err, &chrc_handles);
}
static void vcp_vol_ctlr_state_cb(struct bt_vcp_vol_ctlr *vol_ctlr, int err, uint8_t volume,
uint8_t mute)
{
struct bt_conn *conn;
bt_vcp_vol_ctlr_conn_get(vol_ctlr, &conn);
btp_send_vcp_state_ev(conn, err, volume, mute);
LOG_DBG("VCP Volume CTLR State callback");
}
static void vcp_vol_ctlr_flags_cb(struct bt_vcp_vol_ctlr *vol_ctlr, int err, uint8_t flags)
{
struct bt_conn *conn;
bt_vcp_vol_ctlr_conn_get(vol_ctlr, &conn);
btp_send_vcp_volume_flags_ev(conn, err, flags);
LOG_DBG("VCP CTLR Volume Flags callback");
}
static void vcp_vol_ctlr_vol_down_cb(struct bt_vcp_vol_ctlr *vol_ctlr, int err)
{
struct bt_conn *conn;
bt_vcp_vol_ctlr_conn_get(vol_ctlr, &conn);
btp_send_vcp_procedure_ev(conn, err, BTP_VCP_VOL_CTLR_VOL_DOWN);
LOG_DBG("VCP CTLR Volume down callback");
}
static void vcp_vol_ctlr_vol_up_cb(struct bt_vcp_vol_ctlr *vol_ctlr, int err)
{
struct bt_conn *conn;
bt_vcp_vol_ctlr_conn_get(vol_ctlr, &conn);
btp_send_vcp_procedure_ev(conn, err, BTP_VCP_VOL_CTLR_VOL_UP);
LOG_DBG("VCP CTLR Volume down callback");
}
static void vcp_vol_ctlr_unmute_vol_down_cb(struct bt_vcp_vol_ctlr *vol_ctlr, int err)
{
struct bt_conn *conn;
bt_vcp_vol_ctlr_conn_get(vol_ctlr, &conn);
btp_send_vcp_procedure_ev(conn, err, BTP_VCP_VOL_CTLR_UNMUTE_VOL_DOWN);
LOG_DBG("VCP CTLR Volume down and unmute callback");
}
static void vcp_vol_ctlr_unmute_vol_up_cb(struct bt_vcp_vol_ctlr *vol_ctlr, int err)
{
struct bt_conn *conn;
bt_vcp_vol_ctlr_conn_get(vol_ctlr, &conn);
btp_send_vcp_procedure_ev(conn, err, BTP_VCP_VOL_CTLR_UNMUTE_VOL_UP);
LOG_DBG("VCP CTLR Volume down and unmute callback");
}
static void vcp_vol_ctlr_set_vol_cb(struct bt_vcp_vol_ctlr *vol_ctlr, int err)
{
struct bt_conn *conn;
bt_vcp_vol_ctlr_conn_get(vol_ctlr, &conn);
btp_send_vcp_procedure_ev(conn, err, BTP_VCP_VOL_CTLR_SET_VOL);
LOG_DBG("VCP CTLR Set absolute volume callback");
}
static void vcp_vol_ctlr_unmute_cb(struct bt_vcp_vol_ctlr *vol_ctlr, int err)
{
struct bt_conn *conn;
bt_vcp_vol_ctlr_conn_get(vol_ctlr, &conn);
btp_send_vcp_procedure_ev(conn, err, BTP_VCP_VOL_CTLR_UNMUTE);
LOG_DBG("VCP CTLR Volume down and unmute callback");
}
static void vcp_vol_ctlr_mute_cb(struct bt_vcp_vol_ctlr *vol_ctlr, int err)
{
struct bt_conn *conn;
bt_vcp_vol_ctlr_conn_get(vol_ctlr, &conn);
btp_send_vcp_procedure_ev(conn, err, BTP_VCP_VOL_CTLR_MUTE);
LOG_DBG("VCP CTLR Set absolute volume callback");
}
static struct bt_vcp_vol_ctlr_cb vcp_cbs = {
.discover = vcp_vol_ctlr_discover_cb,
.state = vcp_vol_ctlr_state_cb,
.flags = vcp_vol_ctlr_flags_cb,
.vol_down = vcp_vol_ctlr_vol_down_cb,
.vol_up = vcp_vol_ctlr_vol_up_cb,
.mute = vcp_vol_ctlr_mute_cb,
.unmute = vcp_vol_ctlr_unmute_cb,
.vol_down_unmute = vcp_vol_ctlr_unmute_vol_down_cb,
.vol_up_unmute = vcp_vol_ctlr_unmute_vol_up_cb,
.vol_set = vcp_vol_ctlr_set_vol_cb
};
static uint8_t vcp_discover(const void *cmd, uint16_t cmd_len, void *rsp, uint16_t *rsp_len)
{
const struct btp_vcp_discover_cmd *cp = cmd;
struct bt_conn *conn;
int err;
conn = bt_conn_lookup_addr_le(BT_ID_DEFAULT, &cp->address);
if (!conn) {
LOG_ERR("Unknown connection");
return BTP_STATUS_FAILED;
}
err = bt_vcp_vol_ctlr_discover(conn, &vol_ctlr);
if (err) {
LOG_DBG("Fail: %d", err);
return BTP_STATUS_FAILED;
}
return BTP_STATUS_SUCCESS;
}
static uint8_t vcp_state_read(const void *cmd, uint16_t cmd_len, void *rsp, uint16_t *rsp_len)
{
int err;
LOG_DBG("VCP State read");
err = bt_vcp_vol_ctlr_read_state(vol_ctlr);
if (err) {
return BTP_STATUS_FAILED;
}
return BTP_STATUS_SUCCESS;
}
static uint8_t vcp_volume_flags_read(const void *cmd, uint16_t cmd_len, void *rsp,
uint16_t *rsp_len)
{
int err;
LOG_DBG("VCP Volume Flags read");
err = bt_vcp_vol_ctlr_read_flags(vol_ctlr);
if (err) {
return BTP_STATUS_FAILED;
}
return BTP_STATUS_SUCCESS;
}
static uint8_t vcp_ctlr_vol_down(const void *cmd, uint16_t cmd_len, void *rsp, uint16_t *rsp_len)
{
int err;
LOG_DBG("VCP CTLR Volume down");
err = bt_vcp_vol_ctlr_vol_down(vol_ctlr);
if (err) {
return BTP_STATUS_FAILED;
}
return BTP_STATUS_SUCCESS;
}
static uint8_t vcp_ctlr_vol_up(const void *cmd, uint16_t cmd_len, void *rsp, uint16_t *rsp_len)
{
int err;
LOG_DBG("VCP CTLR Volume up");
err = bt_vcp_vol_ctlr_vol_up(vol_ctlr);
if (err) {
return BTP_STATUS_FAILED;
}
return BTP_STATUS_SUCCESS;
}
static uint8_t vcp_ctlr_unmute_vol_down(const void *cmd, uint16_t cmd_len, void *rsp,
uint16_t *rsp_len)
{
int err;
LOG_DBG("VCP CTLR Unmute, vol down");
err = bt_vcp_vol_ctlr_unmute_vol_down(vol_ctlr);
if (err) {
return BTP_STATUS_FAILED;
}
return BTP_STATUS_SUCCESS;
}
static uint8_t vcp_ctlr_unmute_vol_up(const void *cmd, uint16_t cmd_len, void *rsp,
uint16_t *rsp_len)
{
int err;
LOG_DBG("VCP CTLR Unmute, Volume up");
err = bt_vcp_vol_ctlr_unmute_vol_up(vol_ctlr);
if (err) {
return BTP_STATUS_FAILED;
}
return BTP_STATUS_SUCCESS;
}
static uint8_t vcp_ctlr_set_vol(const void *cmd, uint16_t cmd_len, void *rsp, uint16_t *rsp_len)
{
const struct btp_vcp_ctlr_set_vol_cmd *cp = cmd;
int err;
LOG_DBG("VCP CTLR Set absolute volume %d", cp->volume);
err = bt_vcp_vol_ctlr_set_vol(vol_ctlr, cp->volume);
if (err) {
return BTP_STATUS_FAILED;
}
return BTP_STATUS_SUCCESS;
}
static uint8_t vcp_ctlr_unmute(const void *cmd, uint16_t cmd_len, void *rsp, uint16_t *rsp_len)
{
int err;
LOG_DBG("VCP CTLR Unmute");
err = bt_vcp_vol_ctlr_unmute(vol_ctlr);
if (err) {
return BTP_STATUS_FAILED;
}
return BTP_STATUS_SUCCESS;
}
static uint8_t vcp_ctlr_mute(const void *cmd, uint16_t cmd_len, void *rsp, uint16_t *rsp_len)
{
int err;
LOG_DBG("VCP CTLR Mute");
err = bt_vcp_vol_ctlr_mute(vol_ctlr);
if (err) {
return BTP_STATUS_FAILED;
}
return BTP_STATUS_SUCCESS;
}
static const struct btp_handler vcp_handlers[] = {
{
.opcode = BTP_VCP_READ_SUPPORTED_COMMANDS,
.index = BTP_INDEX_NONE,
.expect_len = 0,
.func = vcp_supported_commands,
},
{
.opcode = BTP_VCP_VOL_CTLR_DISCOVER,
.expect_len = sizeof(struct btp_vcp_discover_cmd),
.func = vcp_discover,
},
{
.opcode = BTP_VCP_VOL_CTLR_STATE_READ,
.expect_len = sizeof(struct btp_vcp_state_read_cmd),
.func = vcp_state_read,
},
{
.opcode = BTP_VCP_VOL_CTLR_FLAGS_READ,
.expect_len = sizeof(struct btp_vcp_flags_read_cmd),
.func = vcp_volume_flags_read,
},
{
.opcode = BTP_VCP_VOL_CTLR_VOL_DOWN,
.expect_len = sizeof(struct btp_vcp_ctlr_vol_down_cmd),
.func = vcp_ctlr_vol_down,
},
{
.opcode = BTP_VCP_VOL_CTLR_VOL_UP,
.expect_len = sizeof(struct btp_vcp_ctlr_vol_up_cmd),
.func = vcp_ctlr_vol_up,
},
{
.opcode = BTP_VCP_VOL_CTLR_UNMUTE_VOL_DOWN,
.expect_len = sizeof(struct btp_vcp_ctlr_unmute_vol_down_cmd),
.func = vcp_ctlr_unmute_vol_down,
},
{
.opcode = BTP_VCP_VOL_CTLR_UNMUTE_VOL_UP,
.expect_len = sizeof(struct btp_vcp_ctlr_unmute_vol_up_cmd),
.func = vcp_ctlr_unmute_vol_up,
},
{
.opcode = BTP_VCP_VOL_CTLR_SET_VOL,
.expect_len = sizeof(struct btp_vcp_ctlr_set_vol_cmd),
.func = vcp_ctlr_set_vol,
},
{
.opcode = BTP_VCP_VOL_CTLR_UNMUTE,
.expect_len = sizeof(struct btp_vcp_ctlr_unmute_cmd),
.func = vcp_ctlr_unmute,
},
{
.opcode = BTP_VCP_VOL_CTLR_MUTE,
.expect_len = sizeof(struct btp_vcp_ctlr_mute_cmd),
.func = vcp_ctlr_mute,
},
};
uint8_t tester_init_vcp(void)
{
int err;
err = bt_vcp_vol_ctlr_cb_register(&vcp_cbs);
if (err) {
LOG_DBG("Failed to register callbacks: %d", err);
return BTP_STATUS_FAILED;
}
tester_register_command_handlers(BTP_SERVICE_ID_VCP, vcp_handlers,
ARRAY_SIZE(vcp_handlers));
return BTP_STATUS_SUCCESS;
}
uint8_t tester_unregister_vcp(void)
{
int err;
err = bt_vcp_vol_ctlr_cb_unregister(&vcp_cbs);
if (err != 0) {
LOG_DBG("Failed to unregister callbacks: %d", err);
return BTP_STATUS_FAILED;
}
return BTP_STATUS_SUCCESS;
}