Bluetooth: Audio: Volume Control Service and client

This commit implements the volume control service (VCS) and
client, The implementation supports and uses the
Audio Input Control Service (AICS) and
Volume Offset Control Service (VOCS) secondary services.

Signed-off-by: Emil Gydesen <emil.gydesen@nordicsemi.no>
This commit is contained in:
Emil Gydesen 2021-04-20 11:00:03 +02:00 committed by Carles Cufí
commit 82a32f5ca5
14 changed files with 4528 additions and 2 deletions

View file

@ -0,0 +1,519 @@
/*
* Copyright (c) 2020-2021 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef ZEPHYR_INCLUDE_BLUETOOTH_SERVICES_VCS_H_
#define ZEPHYR_INCLUDE_BLUETOOTH_SERVICES_VCS_H_
/**
* @brief Volume Control Service (VCS)
*
* @defgroup bt_gatt_vcs Volume Control Service (VCS)
*
* @ingroup bluetooth
* @{
*
* [Experimental] Users should note that the APIs can change
* as a part of ongoing development.
*/
#include <zephyr/types.h>
#include <bluetooth/audio/aics.h>
#include <bluetooth/audio/vocs.h>
#ifdef __cplusplus
extern "C" {
#endif
#if defined(CONFIG_BT_VCS)
#define BT_VCS_VOCS_CNT CONFIG_BT_VCS_VOCS_INSTANCE_COUNT
#define BT_VCS_AICS_CNT CONFIG_BT_VCS_AICS_INSTANCE_COUNT
#else
#define BT_VCS_VOCS_CNT 0
#define BT_VCS_AICS_CNT 0
#endif /* CONFIG_BT_VCS */
/** Volume Control Service Error codes */
#define BT_VCS_ERR_INVALID_COUNTER 0x80
#define BT_VCS_ERR_OP_NOT_SUPPORTED 0x81
/** Register structure for Volume Control Service */
struct bt_vcs_register_param {
/** Register parameters for Volume Offset Control Services */
struct bt_vocs_register_param vocs_param[BT_VCS_VOCS_CNT];
/** Register parameters for Audio Input Control Services */
struct bt_aics_register_param aics_param[BT_VCS_AICS_CNT];
/** Volume Control Service callback structure. */
struct bt_vcs_cb *cb;
};
/**
* @brief Volume Control Service service instance
*
* Used for to represent a Volume Control Service instance, for either a client
* or a server. The instance pointers either represent local server instances,
* or remote service instances.
*/
struct bt_vcs {
/** Number of Volume Offset Control Service instances */
uint8_t vocs_cnt;
/** Array of pointers to Volume Offset Control Service instances */
struct bt_vocs **vocs;
/** Number of Audio Input Control Service instances */
uint8_t aics_cnt;
/** Array of pointers to Audio Input Control Service instances */
struct bt_aics **aics;
};
/**
* @brief Register the Volume Control Service.
*
* This will register and enable the service and make it discoverable by
* clients.
*
* @param param Volume Control Service register parameters.
*
* @return 0 if success, errno on failure.
*/
int bt_vcs_register(struct bt_vcs_register_param *param);
/**
* @brief Get Volume Control Service service pointer.
*
* Returns a pointer to a struct that contains information about the
* Volume Control Service instance, such as pointers to the
* Volume Offset Control Service (Volume Offset Control Service) or
* Audio Input Control Service (AICS) instances.
*
* @param conn Connection to peer device, or NULL to get server value.
* @param[out] service Pointer to store the result in.
*
* @return 0 if success, errno on failure.
*/
int bt_vcs_get(struct bt_conn *conn, struct bt_vcs *service);
/**
* @brief Callback function for bt_vcs_discover.
*
* This callback is only used for the client.
*
* @param conn The connection that was used to discover
* Volume Control Service.
* @param err Error value. 0 on success, GATT error on positive value
* or errno on negative value.
* @param vocs_count Number of Volume Offset Control Service instances
* on peer device.
* @param aics_count Number of Audio Input Control Service instances on
* peer device.
*/
typedef void (*bt_vcs_discover_cb)(struct bt_conn *conn, int err,
uint8_t vocs_count, uint8_t aics_count);
/**
* @brief Callback function for Volume Control Service volume state.
*
* Called when the value is locally read as the server.
* Called when the value is remotely read as the client.
* Called if the value is changed by either the server or client.
*
* @param conn NULL if local server read or write, otherwise the connection
* to the peer device if remotely read or written.
* @param err Error value. 0 on success, GATT error on positive value
* or errno on negative value.
* @param volume The volume of the Volume Control Service server.
* @param mute The mute setting of the Volume Control Service server.
*/
typedef void (*bt_vcs_state_cb)(struct bt_conn *conn, int err, uint8_t volume,
uint8_t mute);
/**
* @brief Callback function for Volume Control Service flags.
*
* Called when the value is locally read as the server.
* Called when the value is remotely read as the client.
* Called if the value is changed by either the server or client.
*
* @param conn NULL if local server read or write, otherwise the connection
* to the peer device if remotely read or written.
* @param err Error value. 0 on success, GATT error on positive value
* or errno on negative value.
* @param flags The flags of the Volume Control Service server.
*/
typedef void (*bt_vcs_flags_cb)(struct bt_conn *conn, int err, uint8_t flags);
/**
* @brief Callback function for writes.
*
* This callback is only used for the client.
*
* @param conn NULL if local server read or write, otherwise the connection
* to the peer device if remotely read or written.
* @param err Error value. 0 on success, GATT error on fail.
*/
typedef void (*bt_vcs_write_cb)(struct bt_conn *conn, int err);
struct bt_vcs_cb {
/* Volume Control Service */
bt_vcs_state_cb state;
bt_vcs_flags_cb flags;
#if defined(CONFIG_BT_VCS_CLIENT)
bt_vcs_discover_cb discover;
bt_vcs_write_cb vol_down;
bt_vcs_write_cb vol_up;
bt_vcs_write_cb mute;
bt_vcs_write_cb unmute;
bt_vcs_write_cb vol_down_unmute;
bt_vcs_write_cb vol_up_unmute;
bt_vcs_write_cb vol_set;
/* Volume Offset Control Service */
struct bt_vocs_cb vocs_cb;
/* Audio Input Control Service */
struct bt_aics_cb aics_cb;
#endif /* CONFIG_BT_VCS_CLIENT */
};
/**
* @brief Discover Volume Control Service and included services.
*
* This will start a GATT discovery and setup handles and subscriptions.
* This shall be called once before any other actions can be
* executed for the peer device.
*
* This shall only be done as the client,
*
* @param conn The connection to discover Volume Control Service for.
*
* @return 0 if success, errno on failure.
*/
int bt_vcs_discover(struct bt_conn *conn);
/**
* @brief Set the Volume Control Service volume step size.
*
* Set the value that the volume changes, when changed relatively with e.g.
* @ref bt_vcs_vol_down or @ref bt_vcs_vol_up.
*
* This can only be done as the server.
*
* @param volume_step The volume step size (1-255).
*
* @return 0 if success, errno on failure.
*/
int bt_vcs_vol_step_set(uint8_t volume_step);
/**
* @brief Read the Volume Control Service volume state.
*
* @param conn Connection to the peer device,
* or NULL to read local server value.
*
* @return 0 if success, errno on failure.
*/
int bt_vcs_vol_get(struct bt_conn *conn);
/**
* @brief Read the Volume Control Service flags.
*
* @param conn Connection to peer device, or NULL to read local server value.
*
* @return 0 if success, errno on failure.
*/
int bt_vcs_flags_get(struct bt_conn *conn);
/**
* @brief Turn the volume down by one step on the server.
*
* @param conn Connection to peer device, or NULL to read local server value.
*
* @return 0 if success, errno on failure.
*/
int bt_vcs_vol_down(struct bt_conn *conn);
/**
* @brief Turn the volume up by one step on the server.
*
* @param conn Connection to peer device, or NULL to read local server value.
*
* @return 0 if success, errno on failure.
*/
int bt_vcs_vol_up(struct bt_conn *conn);
/**
* @brief Turn the volume down and unmute the server.
*
* @param conn Connection to peer device, or NULL to read local server value.
*
* @return 0 if success, errno on failure.
*/
int bt_vcs_unmute_vol_down(struct bt_conn *conn);
/**
* @brief Turn the volume up and unmute the server.
*
* @param conn Connection to peer device, or NULL to read local server value.
*
* @return 0 if success, errno on failure.
*/
int bt_vcs_unmute_vol_up(struct bt_conn *conn);
/**
* @brief Set the volume on the server
*
* @param conn Connection to peer device, or NULL to set local server value.
* @param volume The absolute volume to set.
*
* @return 0 if success, errno on failure.
*/
int bt_vcs_vol_set(struct bt_conn *conn, uint8_t volume);
/**
* @brief Unmute the server.
*
* @param conn Connection to peer device, or NULL to read local server value.
*
* @return 0 if success, errno on failure.
*/
int bt_vcs_unmute(struct bt_conn *conn);
/**
* @brief Mute the server.
*
* @param conn Connection to peer device, or NULL to read local server value.
*
* @return 0 if success, errno on failure.
*/
int bt_vcs_mute(struct bt_conn *conn);
/**
* @brief Read the Volume Offset Control Service offset state.
*
* @param conn Connection to peer device, or NULL to read local server value.
* @param inst Pointer to the Volume Offset Control Service instance.
*
* @return 0 if success, errno on failure.
*/
int bt_vcs_vocs_state_get(struct bt_conn *conn, struct bt_vocs *inst);
/**
* @brief Read the Volume Offset Control Service location.
*
* @param conn Connection to peer device, or NULL to read local server value.
* @param inst Pointer to the Volume Offset Control Service instance.
*
* @return 0 if success, errno on failure.
*/
int bt_vcs_vocs_location_get(struct bt_conn *conn, struct bt_vocs *inst);
/**
* @brief Set the Volume Offset Control Service location.
*
* @param conn Connection to peer device, or NULL to set local server
* value.
* @param inst Pointer to the Volume Offset Control Service instance.
* @param location The location to set.
*
* @return 0 if success, errno on failure.
*/
int bt_vcs_vocs_location_set(struct bt_conn *conn, struct bt_vocs *inst,
uint8_t location);
/**
* @brief Set the Volume Offset Control Service offset state.
*
* @param conn Connection to peer device, or NULL to set local server value.
* @param inst Pointer to the Volume Offset Control Service instance.
* @param offset The offset to set (-255 to 255).
*
* @return 0 if success, errno on failure.
*/
int bt_vcs_vocs_state_set(struct bt_conn *conn, struct bt_vocs *inst,
int16_t offset);
/**
* @brief Read the Volume Offset Control Service output description.
*
* @param conn Connection to peer device, or NULL to read local server value.
* @param inst Pointer to the Volume Offset Control Service instance.
*
* @return 0 if success, errno on failure.
*/
int bt_vcs_vocs_description_get(struct bt_conn *conn, struct bt_vocs *inst);
/**
* @brief Set the Volume Offset Control Service description.
*
* @param conn Connection to peer device, or NULL to set local server
* value.
* @param inst Pointer to the Volume Offset Control Service instance.
* @param description The description to set.
*
* @return 0 if success, errno on failure.
*/
int bt_vcs_vocs_description_set(struct bt_conn *conn, struct bt_vocs *inst,
const char *description);
/**
* @brief Deactivates an Audio Input Control Service instance.
*
* Audio Input Control Services are activated by default, but this will allow
* the server to deactivate an Audio Input Control Service.
*
* @param inst Pointer to the Audio Input Control Service instance.
*
* @return 0 if success, errno on failure.
*/
int bt_vcs_aics_deactivate(struct bt_aics *inst);
/**
* @brief Activates an Audio Input Control Service instance.
*
* Audio Input Control Services are activated by default, but this will allow
* the server to reactivate an Audio Input Control Service instance after it has
* been deactivated with @ref bt_vcs_aics_deactivate.
*
* @param inst Pointer to the Audio Input Control Service instance.
*
* @return 0 if success, errno on failure.
*/
int bt_vcs_aics_activate(struct bt_aics *inst);
/**
* @brief Read the Audio Input Control Service input state.
*
* @param conn Connection to peer device, or NULL to read local server value.
* @param inst Pointer to the Audio Input Control Service instance.
*
* @return 0 if success, errno on failure.
*/
int bt_vcs_aics_state_get(struct bt_conn *conn, struct bt_aics *inst);
/**
* @brief Read the Audio Input Control Service gain settings.
*
* @param conn Connection to peer device, or NULL to read local server value.
* @param inst Pointer to the Audio Input Control Service instance.
*
* @return 0 if success, errno on failure.
*/
int bt_vcs_aics_gain_setting_get(struct bt_conn *conn, struct bt_aics *inst);
/**
* @brief Read the Audio Input Control Service input type.
*
* @param conn Connection to peer device, or NULL to read local server value.
* @param inst Pointer to the Audio Input Control Service instance.
*
* @return 0 if success, errno on failure.
*/
int bt_vcs_aics_type_get(struct bt_conn *conn, struct bt_aics *inst);
/**
* @brief Read the Audio Input Control Service input status.
*
* @param conn Connection to peer device, or NULL to read local server value.
* @param inst Pointer to the Audio Input Control Service instance.
*
* @return 0 if success, errno on failure.
*/
int bt_vcs_aics_status_get(struct bt_conn *conn, struct bt_aics *inst);
/**
* @brief Mute the Audio Input Control Service input.
*
* @param conn Connection to peer device, or NULL to set local server value.
* @param inst Pointer to the Audio Input Control Service instance.
*
* @return 0 if success, errno on failure.
*/
int bt_vcs_aics_mute(struct bt_conn *conn, struct bt_aics *inst);
/**
* @brief Unmute the Audio Input Control Service input.
*
* @param conn Connection to peer device, or NULL to set local server value.
* @param inst Pointer to the Audio Input Control Service instance.
*
* @return 0 if success, errno on failure.
*/
int bt_vcs_aics_unmute(struct bt_conn *conn, struct bt_aics *inst);
/**
* @brief Set input gain to manual.
*
* @param conn Connection to peer device, or NULL to set local server value.
* @param inst Pointer to the Audio Input Control Service instance.
*
* @return 0 if success, errno on failure.
*/
int bt_vcs_aics_manual_gain_set(struct bt_conn *conn, struct bt_aics *inst);
/**
* @brief Set the input gain to automatic.
*
* @param conn Connection to peer device, or NULL to set local server value.
* @param inst Pointer to the Audio Input Control Service instance.
*
* @return 0 if success, errno on failure.
*/
int bt_vcs_aics_automatic_gain_set(struct bt_conn *conn, struct bt_aics *inst);
/**
* @brief Set the input gain.
*
* @param conn Connection to peer device, or NULL to set local server value.
* @param inst Pointer to the Audio Input Control Service instance.
* @param gain The gain in dB to set (-128 to 127).
*
* @return 0 if success, errno on failure.
*/
int bt_vcs_aics_gain_set(struct bt_conn *conn, struct bt_aics *inst,
int8_t gain);
/**
* @brief Read the Audio Input Control Service description.
*
* @param conn Connection to peer device, or NULL to read local server value.
* @param inst Pointer to the Audio Input Control Service instance.
*
* @return 0 if success, errno on failure.
*/
int bt_vcs_aics_description_get(struct bt_conn *conn, struct bt_aics *inst);
/**
* @brief Set the Audio Input Control Service description.
*
* @param conn Connection to peer device, or NULL to set local server
* value.
* @param inst Pointer to the Audio Input Control Service instance.
* @param description The description to set.
*
* @return 0 if success, errno on failure.
*/
int bt_vcs_aics_description_set(struct bt_conn *conn, struct bt_aics *inst,
const char *description);
/**
* @brief Registers the callbacks used by the Volume Control Service client.
*
* @param cb The callback structure.
*
* @return 0 if success, errno on failure.
*/
int bt_vcs_client_cb_register(struct bt_vcs_cb *cb);
#ifdef __cplusplus
}
#endif
/**
* @}
*/
#endif /* ZEPHYR_INCLUDE_BLUETOOTH_SERVICES_VCS_H_ */

View file

@ -167,8 +167,9 @@ typedef void (*bt_vocs_description_cb_t)(struct bt_conn *conn, struct bt_vocs *i
/**
* @brief Callback function for bt_vocs_discover.
*
* This callback will usually be overwritten by the primary service that
* includes the Volume Control Offset Service client.
* This callback should be overwritten by the primary service that
* includes the Volume Control Offset Service client, and should thus not be
* set by the application.
*
* @param conn Connection to peer device, or NULL if local server read.
* @param inst The instance pointer.

View file

@ -419,6 +419,15 @@ struct bt_uuid_128 {
*/
#define BT_UUID_AICS \
BT_UUID_DECLARE_16(BT_UUID_AICS_VAL)
/** @def BT_UUID_VCS_VAL
* @brief Volume Control Service value
*/
#define BT_UUID_VCS_VAL 0x1844
/** @def BT_UUID_VCS
* @brief Volume Control Service
*/
#define BT_UUID_VCS \
BT_UUID_DECLARE_16(BT_UUID_VCS_VAL)
/** @def BT_UUID_VOCS_VAL
* @brief Volume Offset Control Service value
*/
@ -1376,6 +1385,33 @@ struct bt_uuid_128 {
*/
#define BT_UUID_AICS_DESCRIPTION \
BT_UUID_DECLARE_16(BT_UUID_AICS_DESCRIPTION_VAL)
/** @def BT_UUID_VCS_STATE_VAL
* @brief Volume Control Setting value
*/
#define BT_UUID_VCS_STATE_VAL 0x2B7D
/** @def BT_UUID_VCS_STATE
* @brief Volume Control Setting
*/
#define BT_UUID_VCS_STATE \
BT_UUID_DECLARE_16(BT_UUID_VCS_STATE_VAL)
/** @def BT_UUID_VCS_CONTROL_VAL
* @brief Volume Control Control point value
*/
#define BT_UUID_VCS_CONTROL_VAL 0x2B7E
/** @def BT_UUID_VCS_CONTROL
* @brief Volume Control Control point
*/
#define BT_UUID_VCS_CONTROL \
BT_UUID_DECLARE_16(BT_UUID_VCS_CONTROL_VAL)
/** @def BT_UUID_VCS_FLAGS_VAL
* @brief Volume Control Flags value
*/
#define BT_UUID_VCS_FLAGS_VAL 0x2B7F
/** @def BT_UUID_VCS_FLAGS
* @brief Volume Control Flags
*/
#define BT_UUID_VCS_FLAGS \
BT_UUID_DECLARE_16(BT_UUID_VCS_FLAGS_VAL)
/** @def BT_UUID_VOCS_STATE_VAL
* @brief Volume Offset State value
*/

View file

@ -9,3 +9,8 @@ if (CONFIG_BT_AICS OR CONFIG_BT_AICS_CLIENT)
zephyr_library_sources(aics.c)
endif()
zephyr_library_sources_ifdef(CONFIG_BT_AICS_CLIENT aics_client.c)
if (CONFIG_BT_VCS OR CONFIG_BT_VCS_CLIENT)
zephyr_library_sources(vcs.c)
endif()
zephyr_library_sources_ifdef(CONFIG_BT_VCS_CLIENT vcs_client.c)

View file

@ -52,5 +52,6 @@ config BT_AUDIO_DEBUG
source "subsys/bluetooth/audio/Kconfig.vocs"
source "subsys/bluetooth/audio/Kconfig.aics"
source "subsys/bluetooth/audio/Kconfig.vcs"
endif # BT_AUDIO

View file

@ -0,0 +1,113 @@
# Bluetooth Audio - Call control configuration options
#
# Copyright (c) 2020 Bose Corporation
# Copyright (c) 2020-2021 Nordic Semiconductor ASA
#
# SPDX-License-Identifier: Apache-2.0
#
if BT_AUDIO
##################### Volume Control Service #####################
config BT_VCS
bool "Volume Control Service Support"
default n
help
This option enables support for Volume Control Service.
if BT_VCS
config BT_VCS_VOCS_INSTANCE_COUNT
int "Volume Offset Control Service instance count"
default 0
range 0 BT_VOCS_MAX_INSTANCE_COUNT
help
This option sets the number of instances of Volume Offset Control
Services.
config BT_VCS_VOCS
bool # Hidden
default y if BT_VCS_VOCS_INSTANCE_COUNT > 0
help
This hidden option makes it possible to easily check if VOCS is
enabled for VCS.
config BT_VCS_AICS_INSTANCE_COUNT
int "Audio Input Control Service instance count for VCS"
default 0
range 0 BT_AICS_MAX_INSTANCE_COUNT
help
This option sets the number of instances of Audio Input Control
Services for VCS.
config BT_VCS_AICS
bool # Hidden
default y if BT_VCS_AICS_INSTANCE_COUNT > 0
help
This hidden option makes it possible to easily check if AICS is
enabled for VCS.
############# DEBUG #############
config BT_DEBUG_VCS
bool "Volume Control Service debug"
depends on BT_AUDIO_DEBUG
help
Use this option to enable Volume Control Service debug logs for the
Bluetooth Audio functionality.
endif # BT_VCS
##################### Volume Control Profile Client #####################
config BT_VCS_CLIENT
bool "Volume Control Profile Support"
select BT_GATT_CLIENT
default n
help
This option enables support for Volume Control Profile.
if BT_VCS_CLIENT
config BT_VCS_CLIENT_MAX_VOCS_INST
int "Maximum number of VOCS instances to setup"
default 0
range 0 BT_VOCS_CLIENT_MAX_INSTANCE_COUNT
help
Sets the maximum number of Volume Offset Control Service (VOCS)
instances to setup and use.
config BT_VCS_CLIENT_VOCS
bool # Hidden
default y if BT_VCS_CLIENT_MAX_VOCS_INST > 0
help
This hidden option makes it possible to easily check if VOCS is
enabled for VCS client.
config BT_VCS_CLIENT_MAX_AICS_INST
int "Maximum number of AICS instances to setup"
default 0
range 0 3
help
Sets the maximum number of Audio Input Control Service (AICS)
instances to setup and use.
config BT_VCS_CLIENT_AICS
bool # Hidden
default y if BT_VCS_CLIENT_MAX_AICS_INST > 0
help
This hidden option makes it possible to easily check if AICS is
enabled for VCS client.
############# DEBUG #############
config BT_DEBUG_VCS_CLIENT
bool "Volume Control Profile debug"
depends on BT_AUDIO_DEBUG
help
Use this option to enable Volume Control Profile debug logs for the
Bluetooth Audio functionality.
endif # BT_VCS_CLIENT
endif # BT_AUDIO

View file

@ -404,6 +404,12 @@ static void aics_client_write_aics_cp_cb(struct bt_conn *conn, uint8_t err,
BT_DBG("Inst %p: err: %d", inst, cb_err);
/* If the change counter is out of data when a write was attempted from
* the application, we automatically initiate a read to get the newest
* state and try again. Once the change counter has been read, we
* restart the applications write request. If it fails
* the second time, we return an error to the application.
*/
if (cb_err == BT_AICS_ERR_INVALID_COUNTER && inst->cli.cp_retried) {
cb_err = BT_ATT_ERR_UNLIKELY;
} else if (cb_err == BT_AICS_ERR_INVALID_COUNTER && inst->cli.state_handle) {

View file

@ -0,0 +1,915 @@
/* Bluetooth VCS */
/*
* Copyright (c) 2018 Intel Corporation
* Copyright (c) 2019-2020 Bose Corporation
* Copyright (c) 2020-2021 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr.h>
#include <sys/byteorder.h>
#include <device.h>
#include <init.h>
#include <bluetooth/bluetooth.h>
#include <bluetooth/conn.h>
#include <bluetooth/gatt.h>
#include <bluetooth/audio/vcs.h>
#include "vcs_internal.h"
#define BT_DBG_ENABLED IS_ENABLED(CONFIG_BT_DEBUG_VCS)
#define LOG_MODULE_NAME bt_vcs
#include "common/log.h"
#if defined(CONFIG_BT_VCS)
#define VOLUME_DOWN(current_vol) \
((uint8_t)MAX(0, (int)current_vol - vcs_inst.volume_step))
#define VOLUME_UP(current_vol) \
((uint8_t)MIN(UINT8_MAX, (int)current_vol + vcs_inst.volume_step))
#define VALID_VCS_OPCODE(opcode) ((opcode) <= BT_VCS_OPCODE_MUTE)
struct vcs_inst_t {
struct vcs_state state;
uint8_t flags;
struct bt_vcs_cb *cb;
uint8_t volume_step;
struct bt_gatt_service *service_p;
struct bt_vocs *vocs_insts[CONFIG_BT_VCS_VOCS_INSTANCE_COUNT];
struct bt_aics *aics_insts[CONFIG_BT_VCS_AICS_INSTANCE_COUNT];
};
static struct vcs_inst_t vcs_inst = {
.state.volume = 100,
.volume_step = 1,
};
static void volume_state_cfg_changed(const struct bt_gatt_attr *attr,
uint16_t value)
{
BT_DBG("value 0x%04x", value);
}
static ssize_t read_vol_state(struct bt_conn *conn,
const struct bt_gatt_attr *attr, void *buf,
uint16_t len, uint16_t offset)
{
BT_DBG("Volume %u, mute %u, counter %u",
vcs_inst.state.volume, vcs_inst.state.mute,
vcs_inst.state.change_counter);
return bt_gatt_attr_read(conn, attr, buf, len, offset,
&vcs_inst.state, sizeof(vcs_inst.state));
}
static ssize_t write_vcs_control(struct bt_conn *conn,
const struct bt_gatt_attr *attr,
const void *buf, uint16_t len, uint16_t offset,
uint8_t flags)
{
const struct vcs_control_vol *cp_val = buf;
bool notify = false;
bool volume_change = false;
uint8_t opcode;
if (offset > 0) {
return BT_GATT_ERR(BT_ATT_ERR_INVALID_OFFSET);
}
if (len == 0 || buf == NULL) {
return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN);
}
/* Check opcode before length */
if (!VALID_VCS_OPCODE(cp_val->cp.opcode)) {
BT_DBG("Invalid opcode %u", cp_val->cp.opcode);
return BT_GATT_ERR(BT_VCS_ERR_OP_NOT_SUPPORTED);
}
if ((len < sizeof(struct vcs_control)) ||
(len == sizeof(struct vcs_control_vol) &&
cp_val->cp.opcode != BT_VCS_OPCODE_SET_ABS_VOL) ||
(len > sizeof(struct vcs_control_vol))) {
return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN);
}
opcode = cp_val->cp.opcode;
BT_DBG("Opcode %u, counter %u", opcode, cp_val->cp.counter);
if (cp_val->cp.counter != vcs_inst.state.change_counter) {
return BT_GATT_ERR(BT_VCS_ERR_INVALID_COUNTER);
}
switch (opcode) {
case BT_VCS_OPCODE_REL_VOL_DOWN:
BT_DBG("Relative Volume Down (0x%x)", opcode);
if (vcs_inst.state.volume > 0) {
vcs_inst.state.volume = VOLUME_DOWN(vcs_inst.state.volume);
notify = true;
}
volume_change = true;
break;
case BT_VCS_OPCODE_REL_VOL_UP:
BT_DBG("Relative Volume Up (0x%x)", opcode);
if (vcs_inst.state.volume != UINT8_MAX) {
vcs_inst.state.volume = VOLUME_UP(vcs_inst.state.volume);
notify = true;
}
volume_change = true;
break;
case BT_VCS_OPCODE_UNMUTE_REL_VOL_DOWN:
BT_DBG("(Unmute) relative Volume Down (0x%x)", opcode);
if (vcs_inst.state.volume > 0) {
vcs_inst.state.volume = VOLUME_DOWN(vcs_inst.state.volume);
notify = true;
}
if (vcs_inst.state.mute) {
vcs_inst.state.mute = 0;
notify = true;
}
volume_change = true;
break;
case BT_VCS_OPCODE_UNMUTE_REL_VOL_UP:
BT_DBG("(Unmute) relative Volume Up (0x%x)", opcode);
if (vcs_inst.state.volume != UINT8_MAX) {
vcs_inst.state.volume = VOLUME_UP(vcs_inst.state.volume);
notify = true;
}
if (vcs_inst.state.mute) {
vcs_inst.state.mute = 0;
notify = true;
}
volume_change = true;
break;
case BT_VCS_OPCODE_SET_ABS_VOL:
BT_DBG("Set Absolute Volume (0x%x) %u",
opcode, vcs_inst.state.volume);
if (vcs_inst.state.volume != cp_val->volume) {
vcs_inst.state.volume = cp_val->volume;
notify = true;
}
volume_change = true;
break;
case BT_VCS_OPCODE_UNMUTE:
BT_DBG("Unmute (0x%x)", opcode);
if (vcs_inst.state.mute) {
vcs_inst.state.mute = 0;
notify = true;
}
break;
case BT_VCS_OPCODE_MUTE:
BT_DBG("Mute (0x%x)", opcode);
if (vcs_inst.state.mute == 0) {
vcs_inst.state.mute = 1;
notify = true;
}
break;
default:
BT_DBG("Unknown opcode (0x%x)", opcode);
return BT_GATT_ERR(BT_VCS_ERR_OP_NOT_SUPPORTED);
}
if (notify) {
vcs_inst.state.change_counter++;
BT_DBG("New state: volume %u, mute %u, counter %u",
vcs_inst.state.volume, vcs_inst.state.mute,
vcs_inst.state.change_counter);
bt_gatt_notify_uuid(NULL, BT_UUID_VCS_STATE,
vcs_inst.service_p->attrs,
&vcs_inst.state, sizeof(vcs_inst.state));
if (vcs_inst.cb && vcs_inst.cb->state) {
vcs_inst.cb->state(conn, 0, vcs_inst.state.volume,
vcs_inst.state.mute);
}
}
if (volume_change && !vcs_inst.flags) {
vcs_inst.flags = 1;
bt_gatt_notify_uuid(NULL, BT_UUID_VCS_FLAGS,
vcs_inst.service_p->attrs,
&vcs_inst.flags, sizeof(vcs_inst.flags));
if (vcs_inst.cb && vcs_inst.cb->flags) {
vcs_inst.cb->flags(conn, 0, vcs_inst.flags);
}
}
return len;
}
static void flags_cfg_changed(const struct bt_gatt_attr *attr, uint16_t value)
{
BT_DBG("value 0x%04x", value);
}
static ssize_t read_flags(struct bt_conn *conn, const struct bt_gatt_attr *attr,
void *buf, uint16_t len, uint16_t offset)
{
BT_DBG("0x%02x", vcs_inst.flags);
return bt_gatt_attr_read(conn, attr, buf, len, offset, &vcs_inst.flags,
sizeof(vcs_inst.flags));
}
#define DUMMY_INCLUDE(i, _) BT_GATT_INCLUDE_SERVICE(NULL),
#define VOCS_INCLUDES(cnt) UTIL_LISTIFY(cnt, DUMMY_INCLUDE)
#define AICS_INCLUDES(cnt) UTIL_LISTIFY(cnt, DUMMY_INCLUDE)
#define BT_VCS_SERVICE_DEFINITION \
BT_GATT_PRIMARY_SERVICE(BT_UUID_VCS), \
VOCS_INCLUDES(CONFIG_BT_VCS_VOCS_INSTANCE_COUNT) \
AICS_INCLUDES(CONFIG_BT_VCS_AICS_INSTANCE_COUNT) \
BT_GATT_CHARACTERISTIC(BT_UUID_VCS_STATE, \
BT_GATT_CHRC_READ | BT_GATT_CHRC_NOTIFY, \
BT_GATT_PERM_READ_ENCRYPT, \
read_vol_state, NULL, NULL), \
BT_GATT_CCC(volume_state_cfg_changed, \
BT_GATT_PERM_READ | BT_GATT_PERM_WRITE_ENCRYPT), \
BT_GATT_CHARACTERISTIC(BT_UUID_VCS_CONTROL, \
BT_GATT_CHRC_WRITE, \
BT_GATT_PERM_WRITE_ENCRYPT, \
NULL, write_vcs_control, NULL), \
BT_GATT_CHARACTERISTIC(BT_UUID_VCS_FLAGS, \
BT_GATT_CHRC_READ | BT_GATT_CHRC_NOTIFY, \
BT_GATT_PERM_READ_ENCRYPT, \
read_flags, NULL, NULL), \
BT_GATT_CCC(flags_cfg_changed, \
BT_GATT_PERM_READ | BT_GATT_PERM_WRITE_ENCRYPT)
static struct bt_gatt_attr vcs_attrs[] = { BT_VCS_SERVICE_DEFINITION };
static struct bt_gatt_service vcs_svc;
static int prepare_vocs_inst(struct bt_vcs_register_param *param)
{
int err;
int j;
int i;
__ASSERT(param, "NULL param");
for (j = 0, i = 0; i < ARRAY_SIZE(vcs_attrs); i++) {
if (bt_uuid_cmp(vcs_attrs[i].uuid, BT_UUID_GATT_INCLUDE) == 0 &&
!vcs_attrs[i].user_data) {
vcs_inst.vocs_insts[j] = bt_vocs_free_instance_get();
if (vcs_inst.vocs_insts[j] == NULL) {
BT_ERR("Could not get free VOCS instances[%u]",
j);
return -ENOMEM;
}
err = bt_vocs_register(vcs_inst.vocs_insts[j],
&param->vocs_param[j]);
if (err != 0) {
BT_DBG("Could not register VOCS instance[%u]: %d",
j, err);
return err;
}
vcs_attrs[i].user_data = bt_vocs_svc_decl_get(vcs_inst.vocs_insts[j]);
j++;
if (j == CONFIG_BT_VCS_VOCS_INSTANCE_COUNT) {
break;
}
}
}
__ASSERT(j == CONFIG_BT_VCS_VOCS_INSTANCE_COUNT,
"Invalid VOCS instance count");
return 0;
}
static int prepare_aics_inst(struct bt_vcs_register_param *param)
{
int err;
int j;
int i;
__ASSERT(param, "NULL param");
for (j = 0, i = 0; i < ARRAY_SIZE(vcs_attrs); i++) {
if (bt_uuid_cmp(vcs_attrs[i].uuid, BT_UUID_GATT_INCLUDE) == 0 &&
!vcs_attrs[i].user_data) {
vcs_inst.aics_insts[j] = bt_aics_free_instance_get();
if (vcs_inst.aics_insts[j] == NULL) {
BT_ERR("Could not get free AICS instances[%u]",
j);
return -ENOMEM;
}
err = bt_aics_register(vcs_inst.aics_insts[j],
&param->aics_param[j]);
if (err != 0) {
BT_DBG("Could not register AICS instance[%u]: %d",
j, err);
return err;
}
vcs_attrs[i].user_data = bt_aics_svc_decl_get(vcs_inst.aics_insts[j]);
j++;
BT_DBG("AICS P %p", vcs_attrs[i].user_data);
if (j == CONFIG_BT_VCS_AICS_INSTANCE_COUNT) {
break;
}
}
}
__ASSERT(j == CONFIG_BT_VCS_AICS_INSTANCE_COUNT,
"Invalid AICS instance count");
return 0;
}
/****************************** PUBLIC API ******************************/
int bt_vcs_register(struct bt_vcs_register_param *param)
{
int err;
vcs_svc = (struct bt_gatt_service)BT_GATT_SERVICE(vcs_attrs);
if (CONFIG_BT_VCS_VOCS_INSTANCE_COUNT > 0) {
err = prepare_vocs_inst(param);
if (err != 0) {
return err;
}
}
if (CONFIG_BT_VCS_AICS_INSTANCE_COUNT > 0) {
err = prepare_aics_inst(param);
if (err != 0) {
return err;
}
}
vcs_inst.service_p = &vcs_svc;
err = bt_gatt_service_register(&vcs_svc);
if (err != 0) {
BT_DBG("VCS service register failed: %d", err);
}
vcs_inst.cb = param->cb;
return err;
}
int bt_vcs_aics_deactivate(struct bt_aics *inst)
{
if (inst == NULL) {
return -EINVAL;
}
return bt_aics_deactivate(inst);
}
int bt_vcs_aics_activate(struct bt_aics *inst)
{
if (inst == NULL) {
return -EINVAL;
}
return bt_aics_activate(inst);
}
#endif /* CONFIG_BT_VCS */
static bool valid_vocs_inst(struct bt_vocs *vocs)
{
if (vocs == NULL) {
return false;
}
#if defined(CONFIG_BT_VCS)
for (int i = 0; i < ARRAY_SIZE(vcs_inst.vocs_insts); i++) {
if (vcs_inst.vocs_insts[i] == vocs) {
return true;
}
}
#endif /* CONFIG_BT_VCS */
return false;
}
static bool valid_aics_inst(struct bt_aics *aics)
{
if (aics == NULL) {
return false;
}
#if defined(CONFIG_BT_VCS)
for (int i = 0; i < ARRAY_SIZE(vcs_inst.aics_insts); i++) {
if (vcs_inst.aics_insts[i] == aics) {
return true;
}
}
#endif /* CONFIG_BT_VCS */
return false;
}
int bt_vcs_get(struct bt_conn *conn, struct bt_vcs *service)
{
#if defined(CONFIG_BT_VCS_CLIENT)
if (conn != NULL) {
return bt_vcs_client_get(conn, service);
}
#endif /* CONFIG_BT_VCS_CLIENT */
#if defined(CONFIG_BT_VCS)
if (conn == NULL) {
if (service == NULL) {
return -EINVAL;
}
service->vocs_cnt = ARRAY_SIZE(vcs_inst.vocs_insts);
service->vocs = vcs_inst.vocs_insts;
service->aics_cnt = ARRAY_SIZE(vcs_inst.aics_insts);
service->aics = vcs_inst.aics_insts;
return 0;
}
#endif /* CONFIG_BT_VCS */
return -EOPNOTSUPP;
}
int bt_vcs_vol_step_set(uint8_t volume_step)
{
#if defined(CONFIG_BT_VCS)
if (volume_step > 0) {
vcs_inst.volume_step = volume_step;
return 0;
} else {
return -EINVAL;
}
#endif /* CONFIG_BT_VCS */
return -EOPNOTSUPP;
}
int bt_vcs_vol_get(struct bt_conn *conn)
{
#if defined(CONFIG_BT_VCS_CLIENT)
if (conn != NULL) {
return bt_vcs_client_read_vol_state(conn);
}
#endif /* CONFIG_BT_VCS_CLIENT */
#if defined(CONFIG_BT_VCS)
if (conn == NULL) {
if (vcs_inst.cb && vcs_inst.cb->state) {
vcs_inst.cb->state(conn, 0, vcs_inst.state.volume,
vcs_inst.state.mute);
}
return 0;
}
#endif /* CONFIG_BT_VCS */
return -EOPNOTSUPP;
}
int bt_vcs_flags_get(struct bt_conn *conn)
{
#if defined(CONFIG_BT_VCS_CLIENT)
if (conn != NULL) {
return bt_vcs_client_read_flags(conn);
}
#endif /* CONFIG_BT_VCS_CLIENT */
#if defined(CONFIG_BT_VCS)
if (conn == NULL) {
if (vcs_inst.cb && vcs_inst.cb->flags) {
vcs_inst.cb->flags(conn, 0, vcs_inst.flags);
}
return 0;
}
#endif /* CONFIG_BT_VCS */
return -EOPNOTSUPP;
}
int bt_vcs_vol_down(struct bt_conn *conn)
{
#if defined(CONFIG_BT_VCS_CLIENT)
if (conn != NULL) {
return bt_vcs_client_vol_down(conn);
}
#endif /* CONFIG_BT_VCS_CLIENT */
#if defined(CONFIG_BT_VCS)
if (conn == NULL) {
const struct vcs_control cp = {
.opcode = BT_VCS_OPCODE_REL_VOL_DOWN,
.counter = vcs_inst.state.change_counter,
};
int err = write_vcs_control(NULL, NULL, &cp, sizeof(cp), 0, 0);
return err > 0 ? 0 : err;
}
#endif /* CONFIG_BT_VCS */
return -EOPNOTSUPP;
}
int bt_vcs_vol_up(struct bt_conn *conn)
{
#if defined(CONFIG_BT_VCS_CLIENT)
if (conn != NULL) {
return bt_vcs_client_vol_up(conn);
}
#endif /* CONFIG_BT_VCS_CLIENT */
#if defined(CONFIG_BT_VCS)
if (conn == NULL) {
const struct vcs_control cp = {
.opcode = BT_VCS_OPCODE_REL_VOL_UP,
.counter = vcs_inst.state.change_counter,
};
int err = write_vcs_control(NULL, NULL, &cp, sizeof(cp), 0, 0);
return err > 0 ? 0 : err;
}
#endif /* CONFIG_BT_VCS */
return -EOPNOTSUPP;
}
int bt_vcs_unmute_vol_down(struct bt_conn *conn)
{
#if defined(CONFIG_BT_VCS_CLIENT)
if (conn != NULL) {
return bt_vcs_client_unmute_vol_down(conn);
}
#endif /* CONFIG_BT_VCS_CLIENT */
#if defined(CONFIG_BT_VCS)
if (conn == NULL) {
const struct vcs_control cp = {
.opcode = BT_VCS_OPCODE_UNMUTE_REL_VOL_DOWN,
.counter = vcs_inst.state.change_counter,
};
int err = write_vcs_control(NULL, NULL, &cp, sizeof(cp), 0, 0);
return err > 0 ? 0 : err;
}
#endif /* CONFIG_BT_VCS */
return -EOPNOTSUPP;
}
int bt_vcs_unmute_vol_up(struct bt_conn *conn)
{
#if defined(CONFIG_BT_VCS_CLIENT)
if (conn != NULL) {
return bt_vcs_client_unmute_vol_up(conn);
}
#endif /* CONFIG_BT_VCS_CLIENT */
#if defined(CONFIG_BT_VCS)
if (conn == NULL) {
const struct vcs_control cp = {
.opcode = BT_VCS_OPCODE_UNMUTE_REL_VOL_UP,
.counter = vcs_inst.state.change_counter,
};
int err = write_vcs_control(NULL, NULL, &cp, sizeof(cp), 0, 0);
return err > 0 ? 0 : err;
}
#endif /* CONFIG_BT_VCS */
return -EOPNOTSUPP;
}
int bt_vcs_vol_set(struct bt_conn *conn, uint8_t volume)
{
#if defined(CONFIG_BT_VCS_CLIENT)
if (conn != NULL) {
return bt_vcs_client_set_volume(conn, volume);
}
#endif /* CONFIG_BT_VCS_CLIENT */
#if defined(CONFIG_BT_VCS)
if (conn == NULL) {
const struct vcs_control_vol cp = {
.cp = {
.opcode = BT_VCS_OPCODE_SET_ABS_VOL,
.counter = vcs_inst.state.change_counter
},
.volume = volume
};
int err = write_vcs_control(NULL, NULL, &cp, sizeof(cp), 0, 0);
return err > 0 ? 0 : err;
}
#endif /* CONFIG_BT_VCS */
return -EOPNOTSUPP;
}
int bt_vcs_unmute(struct bt_conn *conn)
{
#if defined(CONFIG_BT_VCS_CLIENT)
if (conn != NULL) {
return bt_vcs_client_unmute(conn);
}
#endif /* CONFIG_BT_VCS_CLIENT */
#if defined(CONFIG_BT_VCS)
if (conn == NULL) {
const struct vcs_control cp = {
.opcode = BT_VCS_OPCODE_UNMUTE,
.counter = vcs_inst.state.change_counter,
};
int err = write_vcs_control(NULL, NULL, &cp, sizeof(cp), 0, 0);
return err > 0 ? 0 : err;
}
#endif /* CONFIG_BT_VCS */
return -EOPNOTSUPP;
}
int bt_vcs_mute(struct bt_conn *conn)
{
#if defined(CONFIG_BT_VCS_CLIENT)
if (conn != NULL) {
return bt_vcs_client_mute(conn);
}
#endif /* CONFIG_BT_VCS_CLIENT */
#if defined(CONFIG_BT_VCS)
if (conn == NULL) {
const struct vcs_control cp = {
.opcode = BT_VCS_OPCODE_MUTE,
.counter = vcs_inst.state.change_counter,
};
int err = write_vcs_control(NULL, NULL, &cp, sizeof(cp), 0, 0);
return err > 0 ? 0 : err;
}
#endif /* CONFIG_BT_VCS */
return -EOPNOTSUPP;
}
int bt_vcs_vocs_state_get(struct bt_conn *conn, struct bt_vocs *inst)
{
if (IS_ENABLED(CONFIG_BT_VCS_CLIENT_VOCS) &&
conn && bt_vcs_client_valid_vocs_inst(conn, inst)) {
return bt_vocs_state_get(conn, inst);
}
if (IS_ENABLED(CONFIG_BT_VCS_VOCS) && !conn && valid_vocs_inst(inst)) {
return bt_vocs_state_get(NULL, inst);
}
return -EOPNOTSUPP;
}
int bt_vcs_vocs_location_get(struct bt_conn *conn, struct bt_vocs *inst)
{
if (IS_ENABLED(CONFIG_BT_VCS_CLIENT_VOCS) &&
conn && bt_vcs_client_valid_vocs_inst(conn, inst)) {
return bt_vocs_location_get(conn, inst);
}
if (IS_ENABLED(CONFIG_BT_VCS_VOCS) && !conn && valid_vocs_inst(inst)) {
return bt_vocs_location_get(NULL, inst);
}
return -EOPNOTSUPP;
}
int bt_vcs_vocs_location_set(struct bt_conn *conn, struct bt_vocs *inst,
uint8_t location)
{
if (IS_ENABLED(CONFIG_BT_VCS_CLIENT_VOCS) &&
conn && bt_vcs_client_valid_vocs_inst(conn, inst)) {
return bt_vocs_location_set(conn, inst, location);
}
if (IS_ENABLED(CONFIG_BT_VCS_VOCS) && !conn && valid_vocs_inst(inst)) {
return bt_vocs_location_set(NULL, inst, location);
}
return -EOPNOTSUPP;
}
int bt_vcs_vocs_state_set(struct bt_conn *conn, struct bt_vocs *inst,
int16_t offset)
{
if (IS_ENABLED(CONFIG_BT_VCS_CLIENT_VOCS) &&
conn && bt_vcs_client_valid_vocs_inst(conn, inst)) {
return bt_vocs_state_set(conn, inst, offset);
}
if (IS_ENABLED(CONFIG_BT_VCS_VOCS) && !conn && valid_vocs_inst(inst)) {
return bt_vocs_state_set(NULL, inst, offset);
}
return -EOPNOTSUPP;
}
int bt_vcs_vocs_description_get(struct bt_conn *conn, struct bt_vocs *inst)
{
if (IS_ENABLED(CONFIG_BT_VCS_CLIENT_VOCS) &&
conn && bt_vcs_client_valid_vocs_inst(conn, inst)) {
return bt_vocs_description_get(conn, inst);
}
if (IS_ENABLED(CONFIG_BT_VCS_VOCS) && !conn && valid_vocs_inst(inst)) {
return bt_vocs_description_get(NULL, inst);
}
return -EOPNOTSUPP;
}
int bt_vcs_vocs_description_set(struct bt_conn *conn, struct bt_vocs *inst,
const char *description)
{
if (IS_ENABLED(CONFIG_BT_VCS_CLIENT_VOCS) &&
conn && bt_vcs_client_valid_vocs_inst(conn, inst)) {
return bt_vocs_description_set(conn, inst, description);
}
if (IS_ENABLED(CONFIG_BT_VCS_VOCS) && !conn && valid_vocs_inst(inst)) {
return bt_vocs_description_set(NULL, inst, description);
}
return -EOPNOTSUPP;
}
int bt_vcs_aics_state_get(struct bt_conn *conn, struct bt_aics *inst)
{
if (IS_ENABLED(CONFIG_BT_VCS_CLIENT_AICS) &&
conn && bt_vcs_client_valid_aics_inst(conn, inst)) {
return bt_aics_state_get(conn, inst);
}
if (IS_ENABLED(CONFIG_BT_VCS_AICS) && !conn && valid_aics_inst(inst)) {
return bt_aics_state_get(NULL, inst);
}
return -EOPNOTSUPP;
}
int bt_vcs_aics_gain_setting_get(struct bt_conn *conn, struct bt_aics *inst)
{
if (IS_ENABLED(CONFIG_BT_VCS_CLIENT_AICS) &&
conn && bt_vcs_client_valid_aics_inst(conn, inst)) {
return bt_aics_gain_setting_get(conn, inst);
}
if (IS_ENABLED(CONFIG_BT_VCS_AICS) && !conn && valid_aics_inst(inst)) {
return bt_aics_gain_setting_get(NULL, inst);
}
return -EOPNOTSUPP;
}
int bt_vcs_aics_type_get(struct bt_conn *conn, struct bt_aics *inst)
{
if (IS_ENABLED(CONFIG_BT_VCS_CLIENT_AICS) &&
conn && bt_vcs_client_valid_aics_inst(conn, inst)) {
return bt_aics_type_get(conn, inst);
}
if (IS_ENABLED(CONFIG_BT_VCS_AICS) && !conn && valid_aics_inst(inst)) {
return bt_aics_type_get(NULL, inst);
}
return -EOPNOTSUPP;
}
int bt_vcs_aics_status_get(struct bt_conn *conn, struct bt_aics *inst)
{
if (IS_ENABLED(CONFIG_BT_VCS_CLIENT_AICS) &&
conn && bt_vcs_client_valid_aics_inst(conn, inst)) {
return bt_aics_status_get(conn, inst);
}
if (IS_ENABLED(CONFIG_BT_VCS_AICS) && !conn && valid_aics_inst(inst)) {
return bt_aics_status_get(NULL, inst);
}
return -EOPNOTSUPP;
}
int bt_vcs_aics_unmute(struct bt_conn *conn, struct bt_aics *inst)
{
if (IS_ENABLED(CONFIG_BT_VCS_CLIENT_AICS) &&
conn && bt_vcs_client_valid_aics_inst(conn, inst)) {
return bt_aics_unmute(conn, inst);
}
if (IS_ENABLED(CONFIG_BT_VCS_AICS) && !conn && valid_aics_inst(inst)) {
return bt_aics_unmute(NULL, inst);
}
return -EOPNOTSUPP;
}
int bt_vcs_aics_mute(struct bt_conn *conn, struct bt_aics *inst)
{
if (IS_ENABLED(CONFIG_BT_VCS_CLIENT_AICS) &&
conn && bt_vcs_client_valid_aics_inst(conn, inst)) {
return bt_aics_mute(conn, inst);
}
if (IS_ENABLED(CONFIG_BT_VCS_AICS) && !conn && valid_aics_inst(inst)) {
return bt_aics_mute(NULL, inst);
}
return -EOPNOTSUPP;
}
int bt_vcs_aics_manual_gain_set(struct bt_conn *conn, struct bt_aics *inst)
{
if (IS_ENABLED(CONFIG_BT_VCS_CLIENT_AICS) &&
conn && bt_vcs_client_valid_aics_inst(conn, inst)) {
return bt_aics_manual_gain_set(conn, inst);
}
if (IS_ENABLED(CONFIG_BT_VCS_AICS) && !conn && valid_aics_inst(inst)) {
return bt_aics_manual_gain_set(NULL, inst);
}
return -EOPNOTSUPP;
}
int bt_vcs_aics_automatic_gain_set(struct bt_conn *conn, struct bt_aics *inst)
{
if (IS_ENABLED(CONFIG_BT_VCS_CLIENT_AICS) &&
conn && bt_vcs_client_valid_aics_inst(conn, inst)) {
return bt_aics_automatic_gain_set(conn, inst);
}
if (IS_ENABLED(CONFIG_BT_VCS_AICS) && !conn && valid_aics_inst(inst)) {
return bt_aics_automatic_gain_set(NULL, inst);
}
return -EOPNOTSUPP;
}
int bt_vcs_aics_gain_set(struct bt_conn *conn, struct bt_aics *inst,
int8_t gain)
{
if (IS_ENABLED(CONFIG_BT_VCS_CLIENT_AICS) &&
conn && bt_vcs_client_valid_aics_inst(conn, inst)) {
return bt_aics_gain_set(conn, inst, gain);
}
if (IS_ENABLED(CONFIG_BT_VCS_AICS) && !conn && valid_aics_inst(inst)) {
return bt_aics_gain_set(NULL, inst, gain);
}
return -EOPNOTSUPP;
}
int bt_vcs_aics_description_get(struct bt_conn *conn, struct bt_aics *inst)
{
if (IS_ENABLED(CONFIG_BT_VCS_CLIENT_AICS) &&
conn && bt_vcs_client_valid_aics_inst(conn, inst)) {
return bt_aics_description_get(conn, inst);
}
if (IS_ENABLED(CONFIG_BT_VCS_AICS) && !conn && valid_aics_inst(inst)) {
return bt_aics_description_get(NULL, inst);
}
return -EOPNOTSUPP;
}
int bt_vcs_aics_description_set(struct bt_conn *conn, struct bt_aics *inst,
const char *description)
{
if (IS_ENABLED(CONFIG_BT_VCS_CLIENT_AICS) &&
conn && bt_vcs_client_valid_aics_inst(conn, inst)) {
return bt_aics_description_set(conn, inst, description);
}
if (IS_ENABLED(CONFIG_BT_VCS_AICS) && !conn && valid_aics_inst(inst)) {
return bt_aics_description_set(NULL, inst, description);
}
return -EOPNOTSUPP;
}

View file

@ -0,0 +1,954 @@
/* Bluetooth VCS Client - Volume Offset Control Client */
/*
* Copyright (c) 2020 Bose Corporation
* Copyright (c) 2020-2021 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr.h>
#include <zephyr/types.h>
#include <sys/check.h>
#include <device.h>
#include <init.h>
#include <bluetooth/bluetooth.h>
#include <bluetooth/conn.h>
#include <bluetooth/gatt.h>
#include <bluetooth/audio/vcs.h>
#include "vcs_internal.h"
#include "aics_internal.h"
#define BT_DBG_ENABLED IS_ENABLED(CONFIG_BT_DEBUG_VCS_CLIENT)
#define LOG_MODULE_NAME bt_vcs_client
#include "common/log.h"
struct vcs_instance {
struct vcs_state state;
uint8_t flags;
uint16_t start_handle;
uint16_t end_handle;
uint16_t state_handle;
uint16_t control_handle;
uint16_t flag_handle;
struct bt_gatt_subscribe_params state_sub_params;
struct bt_gatt_subscribe_params flag_sub_params;
bool cp_retried;
bool busy;
struct vcs_control_vol cp_val;
struct bt_gatt_write_params write_params;
struct bt_gatt_read_params read_params;
struct bt_gatt_discover_params discover_params;
struct bt_uuid_16 uuid;
uint8_t vocs_inst_cnt;
struct bt_vocs *vocs[CONFIG_BT_VCS_CLIENT_MAX_VOCS_INST];
uint8_t aics_inst_cnt;
struct bt_aics *aics[CONFIG_BT_VCS_CLIENT_MAX_AICS_INST];
};
/* Callback functions */
static struct bt_vcs_cb *vcs_client_cb;
static struct vcs_instance vcs_insts[CONFIG_BT_MAX_CONN];
static int vcs_client_common_vcs_cp(struct bt_conn *conn, uint8_t opcode);
bool bt_vcs_client_valid_vocs_inst(struct bt_conn *conn, struct bt_vocs *vocs)
{
uint8_t conn_index;
if (conn == NULL) {
return false;
}
conn_index = bt_conn_index(conn);
if (vocs == NULL) {
return false;
}
for (int i = 0; i < ARRAY_SIZE(vcs_insts[conn_index].vocs); i++) {
if (vcs_insts[conn_index].vocs[i] == vocs) {
return true;
}
}
return false;
}
bool bt_vcs_client_valid_aics_inst(struct bt_conn *conn, struct bt_aics *aics)
{
uint8_t conn_index;
if (conn == NULL) {
return false;
}
conn_index = bt_conn_index(conn);
if (aics == NULL) {
return false;
}
for (int i = 0; i < ARRAY_SIZE(vcs_insts[conn_index].aics); i++) {
if (vcs_insts[conn_index].aics[i] == aics) {
return true;
}
}
return false;
}
static uint8_t vcs_client_notify_handler(struct bt_conn *conn,
struct bt_gatt_subscribe_params *params,
const void *data, uint16_t length)
{
uint16_t handle = params->value_handle;
struct vcs_instance *vcs_inst = &vcs_insts[bt_conn_index(conn)];
if (data == NULL) {
return BT_GATT_ITER_CONTINUE;
}
if (handle == vcs_inst->state_handle &&
length == sizeof(vcs_inst->state)) {
memcpy(&vcs_inst->state, data, length);
BT_DBG("Volume %u, mute %u, counter %u",
vcs_inst->state.volume, vcs_inst->state.mute,
vcs_inst->state.change_counter);
if (vcs_client_cb && vcs_client_cb->state) {
vcs_client_cb->state(conn, 0, vcs_inst->state.volume,
vcs_inst->state.mute);
}
} else if (handle == vcs_inst->flag_handle &&
length == sizeof(vcs_inst->flags)) {
memcpy(&vcs_inst->flags, data, length);
BT_DBG("Flags %u", vcs_inst->flags);
if (vcs_client_cb && vcs_client_cb->flags) {
vcs_client_cb->flags(conn, 0, vcs_inst->flags);
}
}
return BT_GATT_ITER_CONTINUE;
}
static uint8_t vcs_client_read_vol_state_cb(struct bt_conn *conn, uint8_t err,
struct bt_gatt_read_params *params,
const void *data, uint16_t length)
{
int cb_err = err;
struct vcs_instance *vcs_inst = &vcs_insts[bt_conn_index(conn)];
vcs_inst->busy = false;
if (cb_err) {
BT_DBG("err: %d", cb_err);
} else if (data != NULL) {
if (length == sizeof(vcs_inst->state)) {
memcpy(&vcs_inst->state, data, length);
BT_DBG("Volume %u, mute %u, counter %u",
vcs_inst->state.volume,
vcs_inst->state.mute,
vcs_inst->state.change_counter);
} else {
BT_DBG("Invalid length %u (expected %zu)",
length, sizeof(vcs_inst->state));
cb_err = BT_ATT_ERR_INVALID_ATTRIBUTE_LEN;
}
}
if (vcs_client_cb && vcs_client_cb->state) {
if (cb_err) {
vcs_client_cb->state(conn, cb_err, 0, 0);
} else {
vcs_client_cb->state(conn, cb_err,
vcs_inst->state.volume,
vcs_inst->state.mute);
}
}
return BT_GATT_ITER_STOP;
}
static uint8_t vcs_client_read_flag_cb(struct bt_conn *conn, uint8_t err,
struct bt_gatt_read_params *params,
const void *data, uint16_t length)
{
int cb_err = err;
struct vcs_instance *vcs_inst = &vcs_insts[bt_conn_index(conn)];
vcs_inst->busy = false;
if (cb_err) {
BT_DBG("err: %d", cb_err);
} else if (data != NULL) {
if (length == sizeof(vcs_inst->flags)) {
memcpy(&vcs_inst->flags, data, length);
BT_DBG("Flags %u", vcs_inst->flags);
} else {
BT_DBG("Invalid length %u (expected %zu)",
length, sizeof(vcs_inst->flags));
cb_err = BT_ATT_ERR_INVALID_ATTRIBUTE_LEN;
}
}
if (vcs_client_cb && vcs_client_cb->flags) {
if (cb_err) {
vcs_client_cb->flags(conn, cb_err, 0);
} else {
vcs_client_cb->flags(conn, cb_err, vcs_inst->flags);
}
}
return BT_GATT_ITER_STOP;
}
static void vcs_cp_notify_app(struct bt_conn *conn, uint8_t opcode, int err)
{
if (vcs_client_cb == NULL) {
return;
}
switch (opcode) {
case BT_VCS_OPCODE_REL_VOL_DOWN:
if (vcs_client_cb->vol_down) {
vcs_client_cb->vol_down(conn, err);
}
break;
case BT_VCS_OPCODE_REL_VOL_UP:
if (vcs_client_cb->vol_up) {
vcs_client_cb->vol_up(conn, err);
}
break;
case BT_VCS_OPCODE_UNMUTE_REL_VOL_DOWN:
if (vcs_client_cb->vol_down_unmute) {
vcs_client_cb->vol_down_unmute(conn, err);
}
break;
case BT_VCS_OPCODE_UNMUTE_REL_VOL_UP:
if (vcs_client_cb->vol_up_unmute) {
vcs_client_cb->vol_up_unmute(conn, err);
}
break;
case BT_VCS_OPCODE_SET_ABS_VOL:
if (vcs_client_cb->vol_set) {
vcs_client_cb->vol_set(conn, err);
}
break;
case BT_VCS_OPCODE_UNMUTE:
if (vcs_client_cb->unmute) {
vcs_client_cb->unmute(conn, err);
}
break;
case BT_VCS_OPCODE_MUTE:
if (vcs_client_cb->mute) {
vcs_client_cb->mute(conn, err);
}
break;
default:
BT_DBG("Unknown opcode 0x%02x", opcode);
break;
}
}
static uint8_t internal_read_vol_state_cb(struct bt_conn *conn, uint8_t err,
struct bt_gatt_read_params *params,
const void *data, uint16_t length)
{
int cb_err = 0;
struct vcs_instance *vcs_inst = &vcs_insts[bt_conn_index(conn)];
uint8_t opcode = vcs_inst->cp_val.cp.opcode;
memset(params, 0, sizeof(*params));
if (err > 0) {
BT_WARN("Volume state read failed: %d", err);
cb_err = BT_ATT_ERR_UNLIKELY;
} else if (data != NULL) {
if (length == sizeof(vcs_inst->state)) {
int write_err;
memcpy(&vcs_inst->state, data, length);
BT_DBG("Volume %u, mute %u, counter %u",
vcs_inst->state.volume,
vcs_inst->state.mute,
vcs_inst->state.change_counter);
/* clear busy flag to reuse function */
vcs_inst->busy = false;
if (opcode == BT_VCS_OPCODE_SET_ABS_VOL) {
write_err = bt_vcs_client_set_volume(conn,
vcs_inst->cp_val.volume);
} else {
write_err = vcs_client_common_vcs_cp(conn,
opcode);
}
if (write_err) {
cb_err = BT_ATT_ERR_UNLIKELY;
}
} else {
BT_DBG("Invalid length %u (expected %zu)",
length, sizeof(vcs_inst->state));
cb_err = BT_ATT_ERR_UNLIKELY;
}
}
if (cb_err) {
vcs_inst->busy = false;
vcs_cp_notify_app(conn, opcode, cb_err);
}
return BT_GATT_ITER_STOP;
}
static void vcs_client_write_vcs_cp_cb(struct bt_conn *conn, uint8_t err,
struct bt_gatt_write_params *params)
{
struct vcs_instance *vcs_inst = &vcs_insts[bt_conn_index(conn)];
uint8_t opcode = vcs_inst->cp_val.cp.opcode;
int cb_err = err;
BT_DBG("err: 0x%02X", err);
memset(params, 0, sizeof(*params));
/* If the change counter is out of data when a write was attempted from
* the application, we automatically initiate a read to get the newest
* state and try again. Once the change counter has been read, we
* restart the applications write request. If it fails
* the second time, we return an error to the application.
*/
if (cb_err == BT_VCS_ERR_INVALID_COUNTER && vcs_inst->cp_retried) {
cb_err = BT_ATT_ERR_UNLIKELY;
} else if (err == BT_VCS_ERR_INVALID_COUNTER &&
vcs_inst->state_handle) {
vcs_inst->read_params.func = internal_read_vol_state_cb;
vcs_inst->read_params.handle_count = 1;
vcs_inst->read_params.single.handle = vcs_inst->state_handle;
vcs_inst->read_params.single.offset = 0U;
cb_err = bt_gatt_read(conn, &vcs_inst->read_params);
if (cb_err) {
BT_WARN("Could not read Volume state: %d", cb_err);
} else {
vcs_inst->cp_retried = true;
/* Wait for read callback */
return;
}
}
vcs_inst->busy = false;
vcs_inst->cp_retried = false;
vcs_cp_notify_app(conn, opcode, err);
}
#if (CONFIG_BT_VCS_CLIENT_MAX_AICS_INST > 0 || CONFIG_BT_VCS_CLIENT_MAX_VOCS_INST > 0)
static uint8_t vcs_discover_include_func(struct bt_conn *conn,
const struct bt_gatt_attr *attr,
struct bt_gatt_discover_params *params)
{
struct bt_gatt_include *include;
uint8_t inst_idx;
int err;
struct vcs_instance *vcs_inst = &vcs_insts[bt_conn_index(conn)];
if (attr == NULL) {
BT_DBG("Discover include complete for VCS: %u AICS and %u VOCS",
vcs_inst->aics_inst_cnt, vcs_inst->vocs_inst_cnt);
(void)memset(params, 0, sizeof(*params));
if (vcs_client_cb && vcs_client_cb->discover) {
/*
* TODO: Validate that all mandatory handles were found
*/
vcs_client_cb->discover(conn, 0,
vcs_inst->vocs_inst_cnt,
vcs_inst->aics_inst_cnt);
}
return BT_GATT_ITER_STOP;
}
BT_DBG("[ATTRIBUTE] handle 0x%04X", attr->handle);
if (params->type == BT_GATT_DISCOVER_INCLUDE) {
uint8_t conn_index = bt_conn_index(conn);
include = (struct bt_gatt_include *)attr->user_data;
BT_DBG("Include UUID %s", bt_uuid_str(include->uuid));
#if CONFIG_BT_VCS_CLIENT_MAX_AICS_INST > 0
if (bt_uuid_cmp(include->uuid, BT_UUID_AICS) == 0 &&
vcs_inst->aics_inst_cnt < CONFIG_BT_VCS_CLIENT_MAX_AICS_INST) {
struct bt_aics_discover_param param = {
.start_handle = include->start_handle,
.end_handle = include->end_handle,
};
/* Update discover params so we can continue where we
* left off after bt_aics_discover
*/
vcs_inst->discover_params.start_handle = attr->handle + 1;
inst_idx = vcs_inst->aics_inst_cnt++;
err = bt_aics_discover(conn,
vcs_insts[conn_index].aics[inst_idx],
&param);
if (err != 0) {
BT_DBG("AICS Discover failed (err %d)", err);
if (vcs_client_cb && vcs_client_cb->discover) {
vcs_client_cb->discover(conn, err, 0,
0);
}
}
return BT_GATT_ITER_STOP;
}
#endif /* CONFIG_BT_VCS_CLIENT_MAX_AICS_INST */
#if CONFIG_BT_VCS_CLIENT_MAX_VOCS_INST > 0
if (bt_uuid_cmp(include->uuid, BT_UUID_VOCS) == 0 &&
vcs_inst->vocs_inst_cnt < CONFIG_BT_VCS_CLIENT_MAX_VOCS_INST) {
struct bt_vocs_discover_param param = {
.start_handle = include->start_handle,
.end_handle = include->end_handle,
};
/* Update discover params so we can continue where we
* left off after bt_vocs_discover
*/
vcs_inst->discover_params.start_handle = attr->handle + 1;
inst_idx = vcs_inst->vocs_inst_cnt++;
err = bt_vocs_discover(conn,
vcs_insts[conn_index].vocs[inst_idx],
&param);
if (err != 0) {
BT_DBG("VOCS Discover failed (err %d)", err);
if (vcs_client_cb && vcs_client_cb->discover) {
vcs_client_cb->discover(conn, err, 0,
0);
}
}
return BT_GATT_ITER_STOP;
}
#endif /* CONFIG_BT_VCS_CLIENT_MAX_VOCS_INST */
}
return BT_GATT_ITER_CONTINUE;
}
#endif /* (CONFIG_BT_VCS_CLIENT_MAX_AICS_INST > 0 || CONFIG_BT_VCS_CLIENT_MAX_VOCS_INST > 0) */
/**
* @brief This will discover all characteristics on the server, retrieving the
* handles of the writeable characteristics and subscribing to all notify and
* indicate characteristics.
*/
static uint8_t vcs_discover_func(struct bt_conn *conn,
const struct bt_gatt_attr *attr,
struct bt_gatt_discover_params *params)
{
int err = 0;
struct bt_gatt_chrc *chrc;
struct bt_gatt_subscribe_params *sub_params = NULL;
struct vcs_instance *vcs_inst = &vcs_insts[bt_conn_index(conn)];
if (attr == NULL) {
BT_DBG("Setup complete for VCS");
(void)memset(params, 0, sizeof(*params));
#if (CONFIG_BT_VCS_CLIENT_MAX_AICS_INST > 0 || CONFIG_BT_VCS_CLIENT_MAX_VOCS_INST > 0)
/* Discover included services */
vcs_inst->discover_params.start_handle = vcs_inst->start_handle;
vcs_inst->discover_params.end_handle = vcs_inst->end_handle;
vcs_inst->discover_params.type = BT_GATT_DISCOVER_INCLUDE;
vcs_inst->discover_params.func = vcs_discover_include_func;
err = bt_gatt_discover(conn, &vcs_inst->discover_params);
if (err != 0) {
BT_DBG("Discover failed (err %d)", err);
if (vcs_client_cb && vcs_client_cb->discover) {
vcs_client_cb->discover(conn, err, 0, 0);
}
}
#else
if (vcs_client_cb && vcs_client_cb->discover) {
vcs_client_cb->discover(conn, err, 0, 0);
}
#endif /* (CONFIG_BT_VCS_CLIENT_MAX_AICS_INST > 0 || CONFIG_BT_VCS_CLIENT_MAX_VOCS_INST > 0) */
return BT_GATT_ITER_STOP;
}
BT_DBG("[ATTRIBUTE] handle 0x%04X", attr->handle);
if (params->type == BT_GATT_DISCOVER_CHARACTERISTIC) {
chrc = (struct bt_gatt_chrc *)attr->user_data;
if (bt_uuid_cmp(chrc->uuid, BT_UUID_VCS_STATE) == 0) {
BT_DBG("Volume state");
vcs_inst->state_handle = chrc->value_handle;
sub_params = &vcs_inst->state_sub_params;
} else if (bt_uuid_cmp(chrc->uuid, BT_UUID_VCS_CONTROL) == 0) {
BT_DBG("Control Point");
vcs_inst->control_handle = chrc->value_handle;
} else if (bt_uuid_cmp(chrc->uuid, BT_UUID_VCS_FLAGS) == 0) {
BT_DBG("Flags");
vcs_inst->flag_handle = chrc->value_handle;
sub_params = &vcs_inst->flag_sub_params;
}
if (sub_params != NULL) {
int err;
sub_params->value = BT_GATT_CCC_NOTIFY;
sub_params->value_handle = chrc->value_handle;
/*
* TODO: Don't assume that CCC is at handle + 2;
* do proper discovery;
*/
sub_params->ccc_handle = attr->handle + 2;
sub_params->notify = vcs_client_notify_handler;
err = bt_gatt_subscribe(conn, sub_params);
if (err == 0) {
BT_DBG("Subscribed to handle 0x%04X",
attr->handle);
} else {
BT_DBG("Could not subscribe to handle 0x%04X",
attr->handle);
}
}
}
return BT_GATT_ITER_CONTINUE;
}
/**
* @brief This will discover all characteristics on the server, retrieving the
* handles of the writeable characteristics and subscribing to all notify and
* indicate characteristics.
*/
static uint8_t primary_discover_func(struct bt_conn *conn,
const struct bt_gatt_attr *attr,
struct bt_gatt_discover_params *params)
{
struct bt_gatt_service_val *prim_service;
struct vcs_instance *vcs_inst = &vcs_insts[bt_conn_index(conn)];
if (attr == NULL) {
BT_DBG("Could not find a VCS instance on the server");
if (vcs_client_cb && vcs_client_cb->discover) {
vcs_client_cb->discover(conn, -ENODATA, 0, 0);
}
return BT_GATT_ITER_STOP;
}
BT_DBG("[ATTRIBUTE] handle 0x%04X", attr->handle);
if (params->type == BT_GATT_DISCOVER_PRIMARY) {
int err;
BT_DBG("Primary discover complete");
prim_service = (struct bt_gatt_service_val *)attr->user_data;
vcs_inst->start_handle = attr->handle + 1;
vcs_inst->end_handle = prim_service->end_handle;
/* Discover characteristics */
vcs_inst->discover_params.uuid = NULL;
vcs_inst->discover_params.start_handle = vcs_inst->start_handle;
vcs_inst->discover_params.end_handle = vcs_inst->end_handle;
vcs_inst->discover_params.type = BT_GATT_DISCOVER_CHARACTERISTIC;
vcs_inst->discover_params.func = vcs_discover_func;
err = bt_gatt_discover(conn, &vcs_inst->discover_params);
if (err != 0) {
BT_DBG("Discover failed (err %d)", err);
if (vcs_client_cb && vcs_client_cb->discover) {
vcs_client_cb->discover(conn, err, 0, 0);
}
}
return BT_GATT_ITER_STOP;
}
return BT_GATT_ITER_CONTINUE;
}
static int vcs_client_common_vcs_cp(struct bt_conn *conn, uint8_t opcode)
{
int err;
struct vcs_instance *vcs_inst;
if (conn == NULL) {
return -ENOTCONN;
}
vcs_inst = &vcs_insts[bt_conn_index(conn)];
if (vcs_inst->control_handle == 0) {
BT_DBG("Handle not set");
return -EINVAL;
} else if (vcs_inst->busy) {
return -EBUSY;
}
vcs_inst->busy = true;
vcs_inst->cp_val.cp.opcode = opcode;
vcs_inst->cp_val.cp.counter = vcs_inst->state.change_counter;
vcs_inst->write_params.offset = 0;
vcs_inst->write_params.data = &vcs_inst->cp_val.cp;
vcs_inst->write_params.length = sizeof(vcs_inst->cp_val.cp);
vcs_inst->write_params.handle = vcs_inst->control_handle;
vcs_inst->write_params.func = vcs_client_write_vcs_cp_cb;
err = bt_gatt_write(conn, &vcs_inst->write_params);
if (err == 0) {
vcs_inst->busy = true;
}
return err;
}
static void aics_discover_cb(struct bt_conn *conn, struct bt_aics *inst,
int err)
{
struct vcs_instance *vcs_inst = &vcs_insts[bt_conn_index(conn)];
if (err == 0) {
/* Continue discovery of included services */
err = bt_gatt_discover(conn, &vcs_inst->discover_params);
}
if (err != 0) {
BT_DBG("Discover failed (err %d)", err);
if (vcs_client_cb && vcs_client_cb->discover) {
vcs_client_cb->discover(conn, err, 0, 0);
}
}
}
static void vocs_discover_cb(struct bt_conn *conn, struct bt_vocs *inst,
int err)
{
struct vcs_instance *vcs_inst = &vcs_insts[bt_conn_index(conn)];
if (err == 0) {
/* Continue discovery of included services */
err = bt_gatt_discover(conn, &vcs_inst->discover_params);
}
if (err != 0) {
BT_DBG("Discover failed (err %d)", err);
if (vcs_client_cb && vcs_client_cb->discover) {
vcs_client_cb->discover(conn, err, 0, 0);
}
}
}
static void vcs_client_reset(struct bt_conn *conn)
{
struct vcs_instance *vcs_inst = &vcs_insts[bt_conn_index(conn)];
memset(&vcs_inst->state, 0, sizeof(vcs_inst->state));
vcs_inst->flags = 0;
vcs_inst->start_handle = 0;
vcs_inst->end_handle = 0;
vcs_inst->state_handle = 0;
vcs_inst->control_handle = 0;
vcs_inst->flag_handle = 0;
vcs_inst->vocs_inst_cnt = 0;
vcs_inst->aics_inst_cnt = 0;
memset(&vcs_inst->discover_params, 0, sizeof(vcs_inst->discover_params));
/* It's okay if these fail */
(void)bt_gatt_unsubscribe(conn, &vcs_inst->state_sub_params);
(void)bt_gatt_unsubscribe(conn, &vcs_inst->flag_sub_params);
}
static void bt_vcs_client_init(void)
{
int i, j;
if (IS_ENABLED(CONFIG_BT_VOCS_CLIENT) &&
CONFIG_BT_VCS_CLIENT_MAX_VOCS_INST > 0) {
for (i = 0; i < ARRAY_SIZE(vcs_insts); i++) {
for (j = 0; j < ARRAY_SIZE(vcs_insts[i].vocs); j++) {
vcs_insts[i].vocs[j] = bt_vocs_client_free_instance_get();
__ASSERT(vcs_insts[i].vocs[j],
"Could not allocate VOCS client instance");
bt_vocs_client_cb_register(vcs_insts[i].vocs[j],
&vcs_client_cb->vocs_cb);
}
}
}
if (IS_ENABLED(CONFIG_BT_AICS_CLIENT) &&
CONFIG_BT_VCS_CLIENT_MAX_AICS_INST > 0) {
for (i = 0; i < ARRAY_SIZE(vcs_insts); i++) {
for (j = 0; j < ARRAY_SIZE(vcs_insts[i].aics); j++) {
vcs_insts[i].aics[j] = bt_aics_client_free_instance_get();
__ASSERT(vcs_insts[i].aics[j],
"Could not allocate AICS client instance");
bt_aics_client_cb_register(vcs_insts[i].aics[j],
&vcs_client_cb->aics_cb);
}
}
}
}
int bt_vcs_discover(struct bt_conn *conn)
{
static bool initialized;
struct vcs_instance *vcs_inst = &vcs_insts[bt_conn_index(conn)];
/*
* This will initiate a discover procedure. The procedure will do the
* following sequence:
* 1) Primary discover for the VCS
* 2) Characteristic discover of the VCS
* 3) Discover services included in VCS (VOCS and AICS)
* 4) For each included service found; discovery of the characteristics
* 5) When everything above have been discovered, the callback is called
*/
if (conn == NULL) {
return -ENOTCONN;
} else if (vcs_inst->busy) {
return -EBUSY;
}
if (!initialized) {
bt_vcs_client_init();
initialized = true;
}
vcs_client_reset(conn);
memcpy(&vcs_inst->uuid, BT_UUID_VCS, sizeof(vcs_inst->uuid));
vcs_inst->discover_params.func = primary_discover_func;
vcs_inst->discover_params.uuid = &vcs_inst->uuid.uuid;
vcs_inst->discover_params.type = BT_GATT_DISCOVER_PRIMARY;
vcs_inst->discover_params.start_handle = BT_ATT_FIRST_ATTTRIBUTE_HANDLE;
vcs_inst->discover_params.end_handle = BT_ATT_LAST_ATTTRIBUTE_HANDLE;
return bt_gatt_discover(conn, &vcs_inst->discover_params);
}
int bt_vcs_client_cb_register(struct bt_vcs_cb *cb)
{
int i, j;
if (IS_ENABLED(CONFIG_BT_VOCS_CLIENT)) {
struct bt_vocs_cb *vocs_cb = NULL;
if (cb != NULL) {
CHECKIF(cb->vocs_cb.discover) {
BT_ERR("VOCS discover callback shall not be set");
return -EINVAL;
}
cb->vocs_cb.discover = vocs_discover_cb;
vocs_cb = &cb->vocs_cb;
}
for (i = 0; i < ARRAY_SIZE(vcs_insts); i++) {
for (j = 0; j < ARRAY_SIZE(vcs_insts[i].vocs); j++) {
if (vcs_insts[i].vocs[j]) {
bt_vocs_client_cb_register(vcs_insts[i].vocs[j],
vocs_cb);
}
}
}
}
if (IS_ENABLED(CONFIG_BT_AICS_CLIENT)) {
struct bt_aics_cb *aics_cb = NULL;
if (cb != NULL) {
CHECKIF(cb->aics_cb.discover) {
BT_ERR("AICS discover callback shall not be set");
return -EINVAL;
}
cb->aics_cb.discover = aics_discover_cb;
aics_cb = &cb->aics_cb;
}
for (i = 0; i < ARRAY_SIZE(vcs_insts); i++) {
for (j = 0; j < ARRAY_SIZE(vcs_insts[i].aics); j++) {
if (vcs_insts[i].aics[j]) {
bt_aics_client_cb_register(vcs_insts[i].aics[j],
aics_cb);
}
}
}
}
vcs_client_cb = cb;
return 0;
}
int bt_vcs_client_get(struct bt_conn *conn, struct bt_vcs *client)
{
uint8_t conn_index;
struct vcs_instance *vcs_inst;
CHECKIF(!client || !conn) {
return -EINVAL;
}
vcs_inst = &vcs_insts[bt_conn_index(conn)];
conn_index = bt_conn_index(conn);
client->vocs_cnt = vcs_inst->vocs_inst_cnt;
client->vocs = vcs_insts[conn_index].vocs;
client->aics_cnt = vcs_inst->aics_inst_cnt;
client->aics = vcs_insts[conn_index].aics;
return 0;
}
int bt_vcs_client_read_vol_state(struct bt_conn *conn)
{
int err;
struct vcs_instance *vcs_inst;
if (conn == NULL) {
return -ENOTCONN;
}
vcs_inst = &vcs_insts[bt_conn_index(conn)];
if (vcs_inst->state_handle == 0) {
BT_DBG("Handle not set");
return -EINVAL;
} else if (vcs_inst->busy) {
return -EBUSY;
}
vcs_inst->read_params.func = vcs_client_read_vol_state_cb;
vcs_inst->read_params.handle_count = 1;
vcs_inst->read_params.single.handle = vcs_inst->state_handle;
vcs_inst->read_params.single.offset = 0U;
err = bt_gatt_read(conn, &vcs_inst->read_params);
if (err == 0) {
vcs_inst->busy = true;
}
return err;
}
int bt_vcs_client_read_flags(struct bt_conn *conn)
{
int err;
struct vcs_instance *vcs_inst;
if (conn == NULL) {
return -ENOTCONN;
}
vcs_inst = &vcs_insts[bt_conn_index(conn)];
if (vcs_inst->flag_handle == 0) {
BT_DBG("Handle not set");
return -EINVAL;
} else if (vcs_inst->busy) {
return -EBUSY;
}
vcs_inst->read_params.func = vcs_client_read_flag_cb;
vcs_inst->read_params.handle_count = 1;
vcs_inst->read_params.single.handle = vcs_inst->flag_handle;
vcs_inst->read_params.single.offset = 0U;
err = bt_gatt_read(conn, &vcs_inst->read_params);
if (err == 0) {
vcs_inst->busy = true;
}
return err;
}
int bt_vcs_client_vol_down(struct bt_conn *conn)
{
return vcs_client_common_vcs_cp(conn, BT_VCS_OPCODE_REL_VOL_DOWN);
}
int bt_vcs_client_vol_up(struct bt_conn *conn)
{
return vcs_client_common_vcs_cp(conn, BT_VCS_OPCODE_REL_VOL_UP);
}
int bt_vcs_client_unmute_vol_down(struct bt_conn *conn)
{
return vcs_client_common_vcs_cp(conn,
BT_VCS_OPCODE_UNMUTE_REL_VOL_DOWN);
}
int bt_vcs_client_unmute_vol_up(struct bt_conn *conn)
{
return vcs_client_common_vcs_cp(conn, BT_VCS_OPCODE_UNMUTE_REL_VOL_UP);
}
int bt_vcs_client_set_volume(struct bt_conn *conn, uint8_t volume)
{
int err;
struct vcs_instance *vcs_inst;
if (conn == NULL) {
return -ENOTCONN;
}
vcs_inst = &vcs_insts[bt_conn_index(conn)];
if (vcs_inst->control_handle == 0) {
BT_DBG("Handle not set");
return -EINVAL;
} else if (vcs_inst->busy) {
return -EBUSY;
}
vcs_inst->cp_val.cp.opcode = BT_VCS_OPCODE_SET_ABS_VOL;
vcs_inst->cp_val.cp.counter = vcs_inst->state.change_counter;
vcs_inst->cp_val.volume = volume;
vcs_inst->busy = true;
vcs_inst->write_params.offset = 0;
vcs_inst->write_params.data = &vcs_inst->cp_val;
vcs_inst->write_params.length = sizeof(vcs_inst->cp_val);
vcs_inst->write_params.handle = vcs_inst->control_handle;
vcs_inst->write_params.func = vcs_client_write_vcs_cp_cb;
err = bt_gatt_write(conn, &vcs_inst->write_params);
if (err == 0) {
vcs_inst->busy = true;
}
return err;
}
int bt_vcs_client_unmute(struct bt_conn *conn)
{
return vcs_client_common_vcs_cp(conn, BT_VCS_OPCODE_UNMUTE);
}
int bt_vcs_client_mute(struct bt_conn *conn)
{
return vcs_client_common_vcs_cp(conn, BT_VCS_OPCODE_MUTE);
}

View file

@ -0,0 +1,52 @@
/**
* @file
* @brief Internal Header for Bluetooth Volumen Control Service (VCS).
*
* Copyright (c) 2020 Bose Corporation
* Copyright (c) 2020-2021 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef ZEPHYR_INCLUDE_BLUETOOTH_AUDIO_VCS_INTERNAL_
#define ZEPHYR_INCLUDE_BLUETOOTH_AUDIO_VCS_INTERNAL_
/* VCS opcodes */
#define BT_VCS_OPCODE_REL_VOL_DOWN 0x00
#define BT_VCS_OPCODE_REL_VOL_UP 0x01
#define BT_VCS_OPCODE_UNMUTE_REL_VOL_DOWN 0x02
#define BT_VCS_OPCODE_UNMUTE_REL_VOL_UP 0x03
#define BT_VCS_OPCODE_SET_ABS_VOL 0x04
#define BT_VCS_OPCODE_UNMUTE 0x05
#define BT_VCS_OPCODE_MUTE 0x06
struct vcs_state {
uint8_t volume;
uint8_t mute;
uint8_t change_counter;
} __packed;
struct vcs_control {
uint8_t opcode;
uint8_t counter;
} __packed;
struct vcs_control_vol {
struct vcs_control cp;
uint8_t volume;
} __packed;
int bt_vcs_client_get(struct bt_conn *conn, struct bt_vcs *client);
int bt_vcs_client_read_vol_state(struct bt_conn *conn);
int bt_vcs_client_read_flags(struct bt_conn *conn);
int bt_vcs_client_vol_down(struct bt_conn *conn);
int bt_vcs_client_vol_up(struct bt_conn *conn);
int bt_vcs_client_unmute_vol_down(struct bt_conn *conn);
int bt_vcs_client_unmute_vol_up(struct bt_conn *conn);
int bt_vcs_client_set_volume(struct bt_conn *conn, uint8_t volume);
int bt_vcs_client_unmute(struct bt_conn *conn);
int bt_vcs_client_mute(struct bt_conn *conn);
bool bt_vcs_client_valid_vocs_inst(struct bt_conn *conn, struct bt_vocs *vocs);
bool bt_vcs_client_valid_aics_inst(struct bt_conn *conn, struct bt_aics *aics);
#endif /* ZEPHYR_INCLUDE_BLUETOOTH_AUDIO_VCS_INTERNAL_*/

View file

@ -25,6 +25,10 @@ zephyr_library_sources_ifdef(
CONFIG_BT_ISO
iso.c
)
zephyr_library_sources_ifdef(
CONFIG_BT_VCS
vcs.c
)
if(CONFIG_BT_CTLR AND CONFIG_BT_LL_SW_SPLIT)
zephyr_library_sources(

View file

@ -0,0 +1,824 @@
/** @file
* @brief Bluetooth VCS server shell.
*
* Copyright (c) 2020 Bose Corporation
* Copyright (c) 2020-2021 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/types.h>
#include <bluetooth/conn.h>
#include <bluetooth/audio/vcs.h>
#include <shell/shell.h>
#include <stdlib.h>
#include <stdio.h>
#include "bt.h"
static struct bt_vcs vcs;
static void vcs_state_cb(struct bt_conn *conn, int err, uint8_t volume,
uint8_t mute)
{
if (err) {
shell_error(ctx_shell, "VCS state get failed (%d)", err);
} else {
shell_print(ctx_shell, "VCS volume %u, mute %u", volume, mute);
}
}
static void vcs_flags_cb(struct bt_conn *conn, int err, uint8_t flags)
{
if (err) {
shell_error(ctx_shell, "VCS flags get failed (%d)", err);
} else {
shell_print(ctx_shell, "VCS flags 0x%02X", flags);
}
}
static void aics_state_cb(struct bt_conn *conn, struct bt_aics *inst, int err,
int8_t gain, uint8_t mute, uint8_t mode)
{
if (err) {
shell_error(ctx_shell,
"AICS state get failed (%d) for inst %p",
err, inst);
} else {
shell_print(ctx_shell,
"AICS inst %p state gain %d, mute %u, mode %u",
inst, gain, mute, mode);
}
}
static void aics_gain_setting_cb(struct bt_conn *conn, struct bt_aics *inst,
int err, uint8_t units, int8_t minimum,
int8_t maximum)
{
if (err) {
shell_error(ctx_shell,
"AICS gain settings get failed (%d) for inst %p",
err, inst);
} else {
shell_print(ctx_shell,
"AICS inst %p gain settings units %u, min %d, max %d",
inst, units, minimum, maximum);
}
}
static void aics_input_type_cb(struct bt_conn *conn, struct bt_aics *inst,
int err, uint8_t input_type)
{
if (err) {
shell_error(ctx_shell,
"AICS input type get failed (%d) for inst %p",
err, inst);
} else {
shell_print(ctx_shell, "AICS inst %p input type %u",
inst, input_type);
}
}
static void aics_status_cb(struct bt_conn *conn, struct bt_aics *inst, int err,
bool active)
{
if (err) {
shell_error(ctx_shell,
"AICS status get failed (%d) for inst %p",
err, inst);
} else {
shell_print(ctx_shell, "AICS inst %p status %s",
inst, active ? "active" : "inactive");
}
}
static void aics_description_cb(struct bt_conn *conn, struct bt_aics *inst,
int err, char *description)
{
if (err) {
shell_error(ctx_shell,
"AICS description get failed (%d) for inst %p",
err, inst);
} else {
shell_print(ctx_shell, "AICS inst %p description %s",
inst, description);
}
}
static void vocs_state_cb(struct bt_conn *conn, struct bt_vocs *inst, int err,
int16_t offset)
{
if (err) {
shell_error(ctx_shell, "VOCS state get failed (%d) for inst %p",
err, inst);
} else {
shell_print(ctx_shell, "VOCS inst %p offset %d", inst, offset);
}
}
static void vocs_location_cb(struct bt_conn *conn, struct bt_vocs *inst,
int err, uint32_t location)
{
if (err) {
shell_error(ctx_shell,
"VOCS location get failed (%d) for inst %p",
err, inst);
} else {
shell_print(ctx_shell, "VOCS inst %p location %u",
inst, location);
}
}
static void vocs_description_cb(struct bt_conn *conn, struct bt_vocs *inst,
int err, char *description)
{
if (err) {
shell_error(ctx_shell,
"VOCS description get failed (%d) for inst %p",
err, inst);
} else {
shell_print(ctx_shell, "VOCS inst %p description %s",
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
};
static int cmd_vcs_init(const struct shell *sh, size_t argc, char **argv)
{
int result;
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];
if (!ctx_shell) {
ctx_shell = sh;
}
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.cb = &vcs_cbs;
result = bt_vcs_register(&vcs_param);
if (result) {
shell_print(sh, "Fail: %d", result);
return result;
}
bt_vcs_get(NULL, &vcs);
return result;
}
static int cmd_vcs_volume_step(const struct shell *sh, size_t argc,
char **argv)
{
int result;
int step = strtol(argv[1], NULL, 0);
if (step > UINT8_MAX || step == 0) {
shell_error(sh, "Step size out of range; 1-255, was %u",
step);
return -ENOEXEC;
}
result = bt_vcs_vol_step_set(step);
if (result) {
shell_print(sh, "Fail: %d", result);
}
return result;
}
static int cmd_vcs_state_get(const struct shell *sh, size_t argc,
char **argv)
{
int result = bt_vcs_vol_get(NULL);
if (result) {
shell_print(sh, "Fail: %d", result);
}
return result;
}
static int cmd_vcs_flags_get(const struct shell *sh, size_t argc,
char **argv)
{
int result = bt_vcs_flags_get(NULL);
if (result) {
shell_print(sh, "Fail: %d", result);
}
return result;
}
static int cmd_vcs_volume_down(const struct shell *sh, size_t argc,
char **argv)
{
int result = bt_vcs_vol_down(NULL);
if (result) {
shell_print(sh, "Fail: %d", result);
}
return result;
}
static int cmd_vcs_volume_up(const struct shell *sh, size_t argc,
char **argv)
{
int result = bt_vcs_vol_up(NULL);
if (result) {
shell_print(sh, "Fail: %d", result);
}
return result;
}
static int cmd_vcs_unmute_volume_down(const struct shell *sh, size_t argc,
char **argv)
{
int result = bt_vcs_unmute_vol_down(NULL);
if (result) {
shell_print(sh, "Fail: %d", result);
}
return result;
}
static int cmd_vcs_unmute_volume_up(const struct shell *sh, size_t argc,
char **argv)
{
int result = bt_vcs_unmute_vol_up(NULL);
if (result) {
shell_print(sh, "Fail: %d", result);
}
return result;
}
static int cmd_vcs_volume_set(const struct shell *sh, size_t argc,
char **argv)
{
int result;
int volume = strtol(argv[1], NULL, 0);
if (volume > UINT8_MAX) {
shell_error(sh, "Volume shall be 0-255, was %d", volume);
return -ENOEXEC;
}
result = bt_vcs_vol_set(NULL, volume);
if (result) {
shell_print(sh, "Fail: %d", result);
}
return result;
}
static int cmd_vcs_unmute(const struct shell *sh, size_t argc, char **argv)
{
int result = bt_vcs_unmute(NULL);
if (result) {
shell_print(sh, "Fail: %d", result);
}
return result;
}
static int cmd_vcs_mute(const struct shell *sh, size_t argc, char **argv)
{
int result = bt_vcs_mute(NULL);
if (result) {
shell_print(sh, "Fail: %d", result);
}
return result;
}
static int cmd_vcs_vocs_state_get(const struct shell *sh, size_t argc,
char **argv)
{
int result;
int index = strtol(argv[1], NULL, 0);
/* TODO: For here, and the following VOCS and AICS, default index to 0 */
if (index > CONFIG_BT_VCS_VOCS_INSTANCE_COUNT) {
shell_error(sh, "Index out of range; 0-%u, was %u",
CONFIG_BT_VCS_VOCS_INSTANCE_COUNT, index);
return -ENOEXEC;
}
result = bt_vcs_vocs_state_get(NULL, vcs.vocs[index]);
if (result) {
shell_print(sh, "Fail: %d", result);
}
return result;
}
static int cmd_vcs_vocs_location_get(const struct shell *sh, size_t argc,
char **argv)
{
int result;
int index = strtol(argv[1], NULL, 0);
if (index > CONFIG_BT_VCS_VOCS_INSTANCE_COUNT) {
shell_error(sh, "Index out of range; 0-%u, was %u",
CONFIG_BT_VCS_VOCS_INSTANCE_COUNT, index);
return -ENOEXEC;
}
result = bt_vcs_vocs_location_get(NULL, vcs.vocs[index]);
if (result) {
shell_print(sh, "Fail: %d", result);
}
return result;
}
static int cmd_vcs_vocs_location_set(const struct shell *sh, size_t argc,
char **argv)
{
int result;
int index = strtol(argv[1], NULL, 0);
int location = strtol(argv[2], NULL, 0);
if (index > CONFIG_BT_VCS_VOCS_INSTANCE_COUNT) {
shell_error(sh, "Index out of range; 0-%u, was %u",
CONFIG_BT_VCS_VOCS_INSTANCE_COUNT, index);
return -ENOEXEC;
}
if (location > UINT16_MAX || location < 0) {
shell_error(sh, "Invalid location (%u-%u), was %u",
0, UINT16_MAX, location);
return -ENOEXEC;
}
result = bt_vcs_vocs_location_set(NULL, vcs.vocs[index], location);
if (result) {
shell_print(sh, "Fail: %d", result);
}
return result;
}
static int cmd_vcs_vocs_offset_set(const struct shell *sh, size_t argc,
char **argv)
{
int result;
int index = strtol(argv[1], NULL, 0);
int offset = strtol(argv[2], NULL, 0);
if (index > CONFIG_BT_VCS_VOCS_INSTANCE_COUNT) {
shell_error(sh, "Index out of range; 0-%u, was %u",
CONFIG_BT_VCS_VOCS_INSTANCE_COUNT, index);
return -ENOEXEC;
}
if (offset > UINT8_MAX || offset < -UINT8_MAX) {
shell_error(sh, "Offset shall be %d-%d, was %d",
-UINT8_MAX, UINT8_MAX, offset);
return -ENOEXEC;
}
result = bt_vcs_vocs_state_set(NULL, vcs.vocs[index], offset);
if (result) {
shell_print(sh, "Fail: %d", result);
}
return result;
}
static int cmd_vcs_vocs_output_description_get(const struct shell *sh,
size_t argc, char **argv)
{
int result;
int index = strtol(argv[1], NULL, 0);
if (index > CONFIG_BT_VCS_VOCS_INSTANCE_COUNT) {
shell_error(sh, "Index out of range; 0-%u, was %u",
CONFIG_BT_VCS_VOCS_INSTANCE_COUNT, index);
return -ENOEXEC;
}
result = bt_vcs_vocs_description_get(NULL, vcs.vocs[index]);
if (result) {
shell_print(sh, "Fail: %d", result);
}
return result;
}
static int cmd_vcs_vocs_output_description_set(const struct shell *sh,
size_t argc, char **argv)
{
int result;
int index = strtol(argv[1], NULL, 0);
char *description = argv[2];
if (index > CONFIG_BT_VCS_VOCS_INSTANCE_COUNT) {
shell_error(sh, "Index out of range; 0-%u, was %u",
CONFIG_BT_VCS_VOCS_INSTANCE_COUNT, index);
return -ENOEXEC;
}
result = bt_vcs_vocs_description_set(NULL, vcs.vocs[index],
description);
if (result) {
shell_print(sh, "Fail: %d", result);
}
return result;
}
static int cmd_vcs_aics_input_state_get(const struct shell *sh, size_t argc,
char **argv)
{
int result;
int index = strtol(argv[1], NULL, 0);
if (index >= vcs.aics_cnt) {
shell_error(sh, "Index shall be less than %u, was %u",
vcs.aics_cnt, index);
return -ENOEXEC;
}
result = bt_vcs_aics_state_get(NULL, vcs.aics[index]);
if (result) {
shell_print(sh, "Fail: %d", result);
}
return result;
}
static int cmd_vcs_aics_gain_setting_get(const struct shell *sh, size_t argc,
char **argv)
{
int result;
int index = strtol(argv[1], NULL, 0);
if (index >= vcs.aics_cnt) {
shell_error(sh, "Index shall be less than %u, was %u",
vcs.aics_cnt, index);
return -ENOEXEC;
}
result = bt_vcs_aics_gain_setting_get(NULL, vcs.aics[index]);
if (result) {
shell_print(sh, "Fail: %d", result);
}
return result;
}
static int cmd_vcs_aics_input_type_get(const struct shell *sh, size_t argc,
char **argv)
{
int result;
int index = strtol(argv[1], NULL, 0);
if (index >= vcs.aics_cnt) {
shell_error(sh, "Index shall be less than %u, was %u",
vcs.aics_cnt, index);
return -ENOEXEC;
}
result = bt_vcs_aics_type_get(NULL, vcs.aics[index]);
if (result) {
shell_print(sh, "Fail: %d", result);
}
return result;
}
static int cmd_vcs_aics_input_status_get(const struct shell *sh, size_t argc,
char **argv)
{
int result;
int index = strtol(argv[1], NULL, 0);
if (index >= vcs.aics_cnt) {
shell_error(sh, "Index shall be less than %u, was %u",
vcs.aics_cnt, index);
return -ENOEXEC;
}
result = bt_vcs_aics_status_get(NULL, vcs.aics[index]);
if (result) {
shell_print(sh, "Fail: %d", result);
}
return result;
}
static int cmd_vcs_aics_input_unmute(const struct shell *sh, size_t argc,
char **argv)
{
int result;
int index = strtol(argv[1], NULL, 0);
if (index >= vcs.aics_cnt) {
shell_error(sh, "Index shall be less than %u, was %u",
vcs.aics_cnt, index);
return -ENOEXEC;
}
result = bt_vcs_aics_unmute(NULL, vcs.aics[index]);
if (result) {
shell_print(sh, "Fail: %d", result);
}
return result;
}
static int cmd_vcs_aics_input_mute(const struct shell *sh, size_t argc,
char **argv)
{
int result;
int index = strtol(argv[1], NULL, 0);
if (index >= vcs.aics_cnt) {
shell_error(sh, "Index shall be less than %u, was %u",
vcs.aics_cnt, index);
return -ENOEXEC;
}
result = bt_vcs_aics_mute(NULL, vcs.aics[index]);
if (result) {
shell_print(sh, "Fail: %d", result);
}
return result;
}
static int cmd_vcs_aics_manual_input_gain_set(const struct shell *sh,
size_t argc, char **argv)
{
int result;
int index = strtol(argv[1], NULL, 0);
if (index >= vcs.aics_cnt) {
shell_error(sh, "Index shall be less than %u, was %u",
vcs.aics_cnt, index);
return -ENOEXEC;
}
result = bt_vcs_aics_manual_gain_set(NULL, vcs.aics[index]);
if (result) {
shell_print(sh, "Fail: %d", result);
}
return result;
}
static int cmd_vcs_aics_automatic_input_gain_set(const struct shell *sh,
size_t argc, char **argv)
{
int result;
int index = strtol(argv[1], NULL, 0);
if (index >= vcs.aics_cnt) {
shell_error(sh, "Index shall be less than %u, was %u",
vcs.aics_cnt, index);
return -ENOEXEC;
}
result = bt_vcs_aics_automatic_gain_set(NULL, vcs.aics[index]);
if (result) {
shell_print(sh, "Fail: %d", result);
}
return result;
}
static int cmd_vcs_aics_gain_set(const struct shell *sh, size_t argc,
char **argv)
{
int result;
int index = strtol(argv[1], NULL, 0);
int gain = strtol(argv[2], NULL, 0);
if (index >= vcs.aics_cnt) {
shell_error(sh, "Index shall be less than %u, was %u",
vcs.aics_cnt, index);
return -ENOEXEC;
}
if (gain > INT8_MAX || gain < INT8_MIN) {
shell_error(sh, "Offset shall be %d-%d, was %d",
INT8_MIN, INT8_MAX, gain);
return -ENOEXEC;
}
result = bt_vcs_aics_gain_set(NULL, vcs.aics[index], gain);
if (result) {
shell_print(sh, "Fail: %d", result);
}
return result;
}
static int cmd_vcs_aics_input_description_get(const struct shell *sh,
size_t argc, char **argv)
{
int result;
int index = strtol(argv[1], NULL, 0);
if (index >= vcs.aics_cnt) {
shell_error(sh, "Index shall be less than %u, was %u",
vcs.aics_cnt, index);
return -ENOEXEC;
}
result = bt_vcs_aics_description_get(NULL, vcs.aics[index]);
if (result) {
shell_print(sh, "Fail: %d", result);
}
return result;
}
static int cmd_vcs_aics_input_description_set(const struct shell *sh,
size_t argc, char **argv)
{
int result;
int index = strtol(argv[1], NULL, 0);
char *description = argv[2];
if (index >= vcs.aics_cnt) {
shell_error(sh, "Index shall be less than %u, was %u",
vcs.aics_cnt, index);
return -ENOEXEC;
}
result = bt_vcs_aics_description_set(NULL, vcs.aics[index], description);
if (result) {
shell_print(sh, "Fail: %d", result);
}
return result;
}
static int cmd_vcs(const struct shell *sh, size_t argc, char **argv)
{
if (argc > 1) {
shell_error(sh, "%s unknown parameter: %s",
argv[0], argv[1]);
} else {
shell_error(sh, "%s Missing subcommand", argv[0]);
}
return -ENOEXEC;
}
SHELL_STATIC_SUBCMD_SET_CREATE(vcs_cmds,
SHELL_CMD_ARG(init, NULL,
"Initialize the service and register callbacks",
cmd_vcs_init, 1, 0),
SHELL_CMD_ARG(state_get, NULL,
"Get volume state of the VCS server. Should be done "
"before sending any control messages",
cmd_vcs_state_get, 1, 0),
SHELL_CMD_ARG(flags_get, NULL,
"Read volume flags",
cmd_vcs_flags_get, 1, 0),
SHELL_CMD_ARG(volume_down, NULL,
"Turn the volume down",
cmd_vcs_volume_down, 1, 0),
SHELL_CMD_ARG(volume_up, NULL,
"Turn the volume up",
cmd_vcs_volume_up, 1, 0),
SHELL_CMD_ARG(unmute_volume_down, NULL,
"Turn the volume down, and unmute",
cmd_vcs_unmute_volume_down, 1, 0),
SHELL_CMD_ARG(unmute_volume_up, NULL,
"Turn the volume up, and unmute",
cmd_vcs_unmute_volume_up, 1, 0),
SHELL_CMD_ARG(volume_set, NULL,
"Set an absolute volume <volume>",
cmd_vcs_volume_set, 2, 0),
SHELL_CMD_ARG(unmute, NULL,
"Unmute",
cmd_vcs_unmute, 1, 0),
SHELL_CMD_ARG(mute, NULL,
"Mute",
cmd_vcs_mute, 1, 0),
SHELL_CMD_ARG(step, NULL,
"Set step size",
cmd_vcs_volume_step, 2, 0),
SHELL_CMD_ARG(vocs_state_get, NULL,
"Get the offset state of a VOCS instance <inst_index>",
cmd_vcs_vocs_state_get, 2, 0),
SHELL_CMD_ARG(vocs_location_get, NULL,
"Get the location of a VOCS instance <inst_index>",
cmd_vcs_vocs_location_get, 2, 0),
SHELL_CMD_ARG(vocs_location_set, NULL,
"Set the location of a VOCS instance <inst_index> "
"<location>",
cmd_vcs_vocs_location_set, 3, 0),
SHELL_CMD_ARG(vocs_offset_set, NULL,
"Set the offset for a VOCS instance <inst_index> "
"<offset>",
cmd_vcs_vocs_offset_set, 3, 0),
SHELL_CMD_ARG(vocs_output_description_get, NULL,
"Get the output description of a VOCS instance "
"<inst_index>",
cmd_vcs_vocs_output_description_get, 2, 0),
SHELL_CMD_ARG(vocs_output_description_set, NULL,
"Set the output description of a VOCS instance "
"<inst_index> <description>",
cmd_vcs_vocs_output_description_set, 3, 0),
SHELL_CMD_ARG(aics_input_state_get, NULL,
"Get the input state of a AICS instance <inst_index>",
cmd_vcs_aics_input_state_get, 2, 0),
SHELL_CMD_ARG(aics_gain_setting_get, NULL,
"Get the gain settings of a AICS instance <inst_index>",
cmd_vcs_aics_gain_setting_get, 2, 0),
SHELL_CMD_ARG(aics_input_type_get, NULL,
"Get the input type of a AICS instance <inst_index>",
cmd_vcs_aics_input_type_get, 2, 0),
SHELL_CMD_ARG(aics_input_status_get, NULL,
"Get the input status of a AICS instance <inst_index>",
cmd_vcs_aics_input_status_get, 2, 0),
SHELL_CMD_ARG(aics_input_unmute, NULL,
"Unmute the input of a AICS instance <inst_index>",
cmd_vcs_aics_input_unmute, 2, 0),
SHELL_CMD_ARG(aics_input_mute, NULL,
"Mute the input of a AICS instance <inst_index>",
cmd_vcs_aics_input_mute, 2, 0),
SHELL_CMD_ARG(aics_manual_input_gain_set, NULL,
"Set the gain mode of a AICS instance to manual "
"<inst_index>",
cmd_vcs_aics_manual_input_gain_set, 2, 0),
SHELL_CMD_ARG(aics_automatic_input_gain_set, NULL,
"Set the gain mode of a AICS instance to automatic "
"<inst_index>",
cmd_vcs_aics_automatic_input_gain_set, 2, 0),
SHELL_CMD_ARG(aics_gain_set, NULL,
"Set the gain in dB of a AICS instance <inst_index> "
"<gain (-128 to 127)>",
cmd_vcs_aics_gain_set, 3, 0),
SHELL_CMD_ARG(aics_input_description_get, NULL,
"Read the input description of a AICS instance "
"<inst_index>",
cmd_vcs_aics_input_description_get, 2, 0),
SHELL_CMD_ARG(aics_input_description_set, NULL,
"Set the input description of a AICS instance "
"<inst_index> <description>",
cmd_vcs_aics_input_description_set, 3, 0),
SHELL_SUBCMD_SET_END
);
SHELL_CMD_ARG_REGISTER(vcs, &vcs_cmds, "Bluetooth VCS shell commands",
cmd_vcs, 1, 1);

File diff suppressed because it is too large Load diff

View file

@ -56,3 +56,10 @@ CONFIG_BT_VOCS_CLIENT_MAX_INSTANCE_COUNT=1
CONFIG_BT_AICS_MAX_INSTANCE_COUNT=1
CONFIG_BT_AICS_CLIENT_MAX_INSTANCE_COUNT=1
CONFIG_BT_VCS=y
CONFIG_BT_VCS_VOCS_INSTANCE_COUNT=1
CONFIG_BT_VCS_AICS_INSTANCE_COUNT=1
CONFIG_BT_VCS_CLIENT=y
CONFIG_BT_VCS_CLIENT_MAX_VOCS_INST=1
CONFIG_BT_VCS_CLIENT_MAX_AICS_INST=1