Bluetooth: Audio: Microphone Input Control Service and Client
This commit implements the microphone input control service (MICS) and client, The implementation supports and uses the Audio Input Control Service (AICS) secondary service. Signed-off-by: Emil Gydesen <emil.gydesen@nordicsemi.no>
This commit is contained in:
parent
03adc1e2bb
commit
2c67bafb7b
9 changed files with 1540 additions and 2 deletions
367
include/bluetooth/audio/mics.h
Normal file
367
include/bluetooth/audio/mics.h
Normal file
|
@ -0,0 +1,367 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2020 Nordic Semiconductor ASA
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef ZEPHYR_INCLUDE_BLUETOOTH_SERVICES_MICS_H_
|
||||||
|
#define ZEPHYR_INCLUDE_BLUETOOTH_SERVICES_MICS_H_
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Microphone Input Control Service (Microphone Input Control Service)
|
||||||
|
*
|
||||||
|
* @defgroup bt_gatt_mics Microphone Input Control Service (Microphone Input Control Service)
|
||||||
|
*
|
||||||
|
* @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>
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined(CONFIG_BT_MICS)
|
||||||
|
#define BT_MICS_AICS_CNT CONFIG_BT_MICS_AICS_INSTANCE_COUNT
|
||||||
|
#else
|
||||||
|
#define BT_MICS_AICS_CNT 0
|
||||||
|
#endif /* CONFIG_BT_MICS */
|
||||||
|
|
||||||
|
/** Application error codes */
|
||||||
|
#define BT_MICS_ERR_MUTE_DISABLED 0x80
|
||||||
|
#define BT_MICS_ERR_VAL_OUT_OF_RANGE 0x81
|
||||||
|
|
||||||
|
/** Microphone Input Control Service mute states */
|
||||||
|
#define BT_MICS_MUTE_UNMUTED 0x00
|
||||||
|
#define BT_MICS_MUTE_MUTED 0x01
|
||||||
|
#define BT_MICS_MUTE_DISABLED 0x02
|
||||||
|
|
||||||
|
/** @brief Register parameters structure for Microphone Input Control Service */
|
||||||
|
struct bt_mics_register_param {
|
||||||
|
/** Register parameter structure for Audio Input Control Services */
|
||||||
|
struct bt_aics_register_param aics_param[BT_MICS_AICS_CNT];
|
||||||
|
|
||||||
|
/** Microphone Input Control Service callback structure. */
|
||||||
|
struct bt_mics_cb *cb;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Microphone Input Control Service instance
|
||||||
|
*
|
||||||
|
* Used for to represent a Microphone Input Control Service, for either
|
||||||
|
* a client or a server instance. The instance pointers either represent local
|
||||||
|
* server instances, or remote service instances.
|
||||||
|
*/
|
||||||
|
struct bt_mics {
|
||||||
|
/** 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 Initialize the Microphone Input Control Service
|
||||||
|
*
|
||||||
|
* This will enable the service and make it discoverable by clients.
|
||||||
|
*
|
||||||
|
* @param param Pointer to a initialization structure.
|
||||||
|
*
|
||||||
|
* @return 0 if success, errno on failure.
|
||||||
|
*/
|
||||||
|
int bt_mics_register(struct bt_mics_register_param *param);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get Microphone Input Control Service pointer
|
||||||
|
*
|
||||||
|
* Returns a pointer to a struct that contains information about the
|
||||||
|
* Microphone Input Control Service instance, such as pointers to the
|
||||||
|
* Audio Input Control Service 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_mics_get(struct bt_conn *conn, struct bt_mics *service);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Callback function for @ref bt_mics_discover.
|
||||||
|
*
|
||||||
|
* This callback is only used for the client.
|
||||||
|
*
|
||||||
|
* @param conn The connection that was used to discover
|
||||||
|
* Microphone Input Control Service.
|
||||||
|
* @param err Error value. 0 on success, GATT error or errno on fail.
|
||||||
|
* @param aics_count Number of Audio Input Control Service instances on
|
||||||
|
* peer device.
|
||||||
|
*/
|
||||||
|
typedef void (*bt_mics_discover_cb)(struct bt_conn *conn, int err,
|
||||||
|
uint8_t aics_count);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Callback function for Microphone Input Control Service mute.
|
||||||
|
*
|
||||||
|
* Called when the value is read,
|
||||||
|
* or if the value is changed by either the server or client.
|
||||||
|
*
|
||||||
|
* @param conn Connection to peer device, or NULL if local server read.
|
||||||
|
* @param err Error value. 0 on success, GATT error or errno on fail.
|
||||||
|
* For notifications, this will always be 0. For reads, if
|
||||||
|
* this is 0, the @p volume and @p mute values are the last
|
||||||
|
* known values.
|
||||||
|
* @param mute The mute setting of the Microphone Input Control Service.
|
||||||
|
*/
|
||||||
|
typedef void (*bt_mics_mute_read_cb)(struct bt_conn *conn, int err,
|
||||||
|
uint8_t mute);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Callback function for Microphone Input Control Service mute/unmute.
|
||||||
|
*
|
||||||
|
* @param conn Connection to peer device, or NULL if local server read.
|
||||||
|
* @param err Error value. 0 on success, GATT error or errno on fail.
|
||||||
|
* @param req_val The requested mute value.
|
||||||
|
*/
|
||||||
|
typedef void (*bt_mics_mute_write_cb)(struct bt_conn *conn, int err);
|
||||||
|
|
||||||
|
struct bt_mics_cb {
|
||||||
|
bt_mics_mute_read_cb mute;
|
||||||
|
|
||||||
|
#if defined(CONFIG_BT_MICS_CLIENT)
|
||||||
|
bt_mics_discover_cb discover;
|
||||||
|
bt_mics_mute_write_cb mute_write;
|
||||||
|
bt_mics_mute_write_cb unmute_write;
|
||||||
|
|
||||||
|
/** Audio Input Control Service client callback */
|
||||||
|
struct bt_aics_cb aics_cb;
|
||||||
|
#endif /* CONFIG_BT_MICS_CLIENT */
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Discover Microphone Input Control Service
|
||||||
|
*
|
||||||
|
* 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 initialize the profile for.
|
||||||
|
*
|
||||||
|
* @return 0 on success, GATT error value on fail.
|
||||||
|
*/
|
||||||
|
int bt_mics_discover(struct bt_conn *conn);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Unmute the server.
|
||||||
|
*
|
||||||
|
* @param conn Connection to peer device, or NULL to set local server value.
|
||||||
|
*
|
||||||
|
* @return 0 on success, GATT error value on fail.
|
||||||
|
*/
|
||||||
|
int bt_mics_unmute(struct bt_conn *conn);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Mute the server.
|
||||||
|
*
|
||||||
|
* @param conn Connection to peer device, or NULL to set local server value.
|
||||||
|
*
|
||||||
|
* @return 0 on success, GATT error value on fail.
|
||||||
|
*/
|
||||||
|
int bt_mics_mute(struct bt_conn *conn);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Disable the mute functionality.
|
||||||
|
*
|
||||||
|
* Can be reenabled by called @ref bt_mics_mute or @ref bt_mics_unmute.
|
||||||
|
* This can only be done as the server.
|
||||||
|
*
|
||||||
|
* @return 0 on success, GATT error value on fail.
|
||||||
|
*/
|
||||||
|
int bt_mics_mute_disable(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Read the mute state of a Microphone Input Control Service.
|
||||||
|
*
|
||||||
|
* @param conn Connection to peer device, or NULL to read local server value.
|
||||||
|
*
|
||||||
|
* @return 0 on success, GATT error value on fail.
|
||||||
|
*/
|
||||||
|
int bt_mics_mute_get(struct bt_conn *conn);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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 on success, GATT error value on fail.
|
||||||
|
*/
|
||||||
|
int bt_mics_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 on success, GATT error value on fail.
|
||||||
|
*/
|
||||||
|
int bt_mics_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 on success, GATT error value on fail.
|
||||||
|
*/
|
||||||
|
int bt_mics_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 on success, GATT error value on fail.
|
||||||
|
*/
|
||||||
|
int bt_mics_aics_status_get(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 on success, GATT error value on fail.
|
||||||
|
*/
|
||||||
|
int bt_mics_aics_unmute(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 on success, GATT error value on fail.
|
||||||
|
*/
|
||||||
|
int bt_mics_aics_mute(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 on success, GATT error value on fail.
|
||||||
|
*/
|
||||||
|
int bt_mics_aics_manual_gain_set(struct bt_conn *conn, struct bt_aics *inst);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Set Audio Input Control Service 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 on success, GATT error value on fail.
|
||||||
|
*/
|
||||||
|
int bt_mics_aics_automatic_gain_set(struct bt_conn *conn, struct bt_aics *inst);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Set Audio Input Control Service 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 on success, GATT error value on fail.
|
||||||
|
*/
|
||||||
|
int bt_mics_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 on success, GATT error value on fail.
|
||||||
|
*/
|
||||||
|
int bt_mics_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 on success, GATT error value on fail.
|
||||||
|
*/
|
||||||
|
int bt_mics_aics_description_set(struct bt_conn *conn, struct bt_aics *inst,
|
||||||
|
const char *description);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Deactivates a Audio Input Control Service instance.
|
||||||
|
*
|
||||||
|
* Audio Input Control Services are activated by default, but this will allow
|
||||||
|
* the server deactivate a Audio Input Control Service.
|
||||||
|
* This can only be done as the server.
|
||||||
|
*
|
||||||
|
* @param inst Pointer to the Audio Input Control Service instance.
|
||||||
|
*
|
||||||
|
* @return 0 if success, errno on failure.
|
||||||
|
*/
|
||||||
|
int bt_mics_aics_deactivate(struct bt_aics *inst);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Activates a Audio Input Control Service instance.
|
||||||
|
*
|
||||||
|
* Audio Input Control Services are activated by default, but this will allow
|
||||||
|
* the server reactivate a Audio Input Control Service instance after it has
|
||||||
|
* been deactivated with @ref bt_mics_aics_deactivate.
|
||||||
|
* This can only be done as the server.
|
||||||
|
*
|
||||||
|
* @param inst Pointer to the Audio Input Control Service instance.
|
||||||
|
*
|
||||||
|
* @return 0 if success, errno on failure.
|
||||||
|
*/
|
||||||
|
int bt_mics_aics_activate(struct bt_aics *inst);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Registers the callbacks used by Microphone Input Control Service client.
|
||||||
|
*
|
||||||
|
* This can only be done as the client.
|
||||||
|
*
|
||||||
|
* @param cb The callback structure.
|
||||||
|
*
|
||||||
|
* @return 0 if success, errno on failure.
|
||||||
|
*/
|
||||||
|
int bt_mics_client_cb_register(struct bt_mics_cb *cb);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @}
|
||||||
|
*/
|
||||||
|
|
||||||
|
#endif /* ZEPHYR_INCLUDE_BLUETOOTH_SERVICES_MICS_H_ */
|
|
@ -437,6 +437,15 @@ struct bt_uuid_128 {
|
||||||
*/
|
*/
|
||||||
#define BT_UUID_VOCS \
|
#define BT_UUID_VOCS \
|
||||||
BT_UUID_DECLARE_16(BT_UUID_VOCS_VAL)
|
BT_UUID_DECLARE_16(BT_UUID_VOCS_VAL)
|
||||||
|
/** @def BT_UUID_MICS_VAL
|
||||||
|
* @brief Microphone Input Control Service value
|
||||||
|
*/
|
||||||
|
#define BT_UUID_MICS_VAL 0x184D
|
||||||
|
/** @def BT_UUID_MICS
|
||||||
|
* @brief Microphone Input Control Service
|
||||||
|
*/
|
||||||
|
#define BT_UUID_MICS \
|
||||||
|
BT_UUID_DECLARE_16(BT_UUID_MICS_VAL)
|
||||||
/** @def BT_UUID_GATT_PRIMARY_VAL
|
/** @def BT_UUID_GATT_PRIMARY_VAL
|
||||||
* @brief GATT Primary Service UUID value
|
* @brief GATT Primary Service UUID value
|
||||||
*/
|
*/
|
||||||
|
@ -1448,6 +1457,15 @@ struct bt_uuid_128 {
|
||||||
*/
|
*/
|
||||||
#define BT_UUID_VOCS_DESCRIPTION \
|
#define BT_UUID_VOCS_DESCRIPTION \
|
||||||
BT_UUID_DECLARE_16(BT_UUID_VOCS_DESCRIPTION_VAL)
|
BT_UUID_DECLARE_16(BT_UUID_VOCS_DESCRIPTION_VAL)
|
||||||
|
/** @def BT_UUID_MICS_MUTE_VAL
|
||||||
|
* @brief Microphone Input Control Service Mute value
|
||||||
|
*/
|
||||||
|
#define BT_UUID_MICS_MUTE_VAL 0x2BC3
|
||||||
|
/** @def BT_UUID_MICS_MUTE
|
||||||
|
* @brief Microphone Input Control Service Mute
|
||||||
|
*/
|
||||||
|
#define BT_UUID_MICS_MUTE \
|
||||||
|
BT_UUID_DECLARE_16(BT_UUID_MICS_MUTE_VAL)
|
||||||
/*
|
/*
|
||||||
* Protocol UUIDs
|
* Protocol UUIDs
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -14,3 +14,8 @@ if (CONFIG_BT_VCS OR CONFIG_BT_VCS_CLIENT)
|
||||||
zephyr_library_sources(vcs.c)
|
zephyr_library_sources(vcs.c)
|
||||||
endif()
|
endif()
|
||||||
zephyr_library_sources_ifdef(CONFIG_BT_VCS_CLIENT vcs_client.c)
|
zephyr_library_sources_ifdef(CONFIG_BT_VCS_CLIENT vcs_client.c)
|
||||||
|
|
||||||
|
if (CONFIG_BT_MICS OR CONFIG_BT_MICS_CLIENT)
|
||||||
|
zephyr_library_sources(mics.c)
|
||||||
|
endif()
|
||||||
|
zephyr_library_sources_ifdef(CONFIG_BT_MICS_CLIENT mics_client.c)
|
||||||
|
|
|
@ -53,5 +53,6 @@ config BT_AUDIO_DEBUG
|
||||||
source "subsys/bluetooth/audio/Kconfig.vocs"
|
source "subsys/bluetooth/audio/Kconfig.vocs"
|
||||||
source "subsys/bluetooth/audio/Kconfig.aics"
|
source "subsys/bluetooth/audio/Kconfig.aics"
|
||||||
source "subsys/bluetooth/audio/Kconfig.vcs"
|
source "subsys/bluetooth/audio/Kconfig.vcs"
|
||||||
|
source "subsys/bluetooth/audio/Kconfig.mics"
|
||||||
|
|
||||||
endif # BT_AUDIO
|
endif # BT_AUDIO
|
||||||
|
|
84
subsys/bluetooth/audio/Kconfig.mics
Normal file
84
subsys/bluetooth/audio/Kconfig.mics
Normal file
|
@ -0,0 +1,84 @@
|
||||||
|
# Bluetooth Audio - Microphone Input Control Service options
|
||||||
|
#
|
||||||
|
# Copyright (c) 2020 Bose Corporation
|
||||||
|
# Copyright (c) 2020-2021 Nordic Semiconductor ASA
|
||||||
|
#
|
||||||
|
# SPDX-License-Identifier: Apache-2.0
|
||||||
|
#
|
||||||
|
|
||||||
|
if BT_AUDIO
|
||||||
|
|
||||||
|
##################### Microphone Input Control Service #####################
|
||||||
|
|
||||||
|
config BT_MICS
|
||||||
|
bool "Microphone Input Control Service Support"
|
||||||
|
default n
|
||||||
|
help
|
||||||
|
This option enables support for Microphone Input Control Service.
|
||||||
|
|
||||||
|
if BT_MICS
|
||||||
|
|
||||||
|
config BT_MICS_AICS_INSTANCE_COUNT
|
||||||
|
int "Audio Input Control Service instance count for Microphone Input Control Service"
|
||||||
|
default 0
|
||||||
|
range 0 BT_AICS_MAX_INSTANCE_COUNT
|
||||||
|
help
|
||||||
|
This option sets the number of instances of Audio Input Control
|
||||||
|
Services for MICS.
|
||||||
|
|
||||||
|
config BT_MICS_AICS
|
||||||
|
bool # Hidden
|
||||||
|
default y if BT_MICS_AICS_INSTANCE_COUNT > 0
|
||||||
|
help
|
||||||
|
This hidden option makes it possible to easily check if AICS is
|
||||||
|
enabled for MICS.
|
||||||
|
|
||||||
|
############# DEBUG #############
|
||||||
|
|
||||||
|
config BT_DEBUG_MICS
|
||||||
|
bool "Microphone Input Control Service debug"
|
||||||
|
depends on BT_AUDIO_DEBUG
|
||||||
|
help
|
||||||
|
Use this option to enable Microphone Input Control Service debug logs
|
||||||
|
for the Bluetooth Audio functionality.
|
||||||
|
|
||||||
|
endif # BT_MICS
|
||||||
|
|
||||||
|
##################### Microphone Control Profile Client #####################
|
||||||
|
|
||||||
|
config BT_MICS_CLIENT
|
||||||
|
bool "Microphone Control Profile Support"
|
||||||
|
select BT_GATT_CLIENT
|
||||||
|
select BT_GATT_AUTO_DISCOVER_CCC
|
||||||
|
help
|
||||||
|
This option enables support for Microphone Control Profile.
|
||||||
|
|
||||||
|
if BT_MICS_CLIENT
|
||||||
|
|
||||||
|
config BT_MICS_CLIENT_MAX_AICS_INST
|
||||||
|
int "Maximum number of Audio Input Control Service 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_MICS_CLIENT_AICS
|
||||||
|
bool # Hidden
|
||||||
|
default y if BT_MICS_CLIENT_MAX_AICS_INST > 0
|
||||||
|
help
|
||||||
|
This hidden option makes it possible to easily check if AICS is
|
||||||
|
enabled for MICS client.
|
||||||
|
|
||||||
|
############# DEBUG #############
|
||||||
|
|
||||||
|
config BT_DEBUG_MICS_CLIENT
|
||||||
|
bool "Microphone Control Profile debug"
|
||||||
|
depends on BT_AUDIO_DEBUG
|
||||||
|
help
|
||||||
|
Use this option to enable Microphone Control Profile debug logs for
|
||||||
|
the Bluetooth Audio functionality.
|
||||||
|
|
||||||
|
endif # BT_MICS_CLIENT
|
||||||
|
|
||||||
|
endif # BT_AUDIO
|
496
subsys/bluetooth/audio/mics.c
Normal file
496
subsys/bluetooth/audio/mics.c
Normal file
|
@ -0,0 +1,496 @@
|
||||||
|
/* Bluetooth MICS
|
||||||
|
*
|
||||||
|
* Copyright (c) 2020 Bose Corporation
|
||||||
|
* Copyright (c) 2020-2021 Nordic Semiconductor ASA
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <zephyr.h>
|
||||||
|
#include <sys/byteorder.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/mics.h>
|
||||||
|
#include <bluetooth/audio/aics.h>
|
||||||
|
|
||||||
|
#include "mics_internal.h"
|
||||||
|
|
||||||
|
#define BT_DBG_ENABLED IS_ENABLED(CONFIG_BT_DEBUG_MICS)
|
||||||
|
#define LOG_MODULE_NAME bt_mics
|
||||||
|
#include "common/log.h"
|
||||||
|
|
||||||
|
#if defined(CONFIG_BT_MICS)
|
||||||
|
struct mics_instance {
|
||||||
|
uint8_t mute;
|
||||||
|
struct bt_mics_cb *cb;
|
||||||
|
struct bt_gatt_service *service_p;
|
||||||
|
struct bt_aics *aics_insts[CONFIG_BT_MICS_AICS_INSTANCE_COUNT];
|
||||||
|
};
|
||||||
|
|
||||||
|
static struct mics_instance mics_inst;
|
||||||
|
|
||||||
|
static void mute_cfg_changed(const struct bt_gatt_attr *attr, uint16_t value)
|
||||||
|
{
|
||||||
|
BT_DBG("value 0x%04x", value);
|
||||||
|
}
|
||||||
|
|
||||||
|
static ssize_t read_mute(struct bt_conn *conn,
|
||||||
|
const struct bt_gatt_attr *attr, void *buf,
|
||||||
|
uint16_t len, uint16_t offset)
|
||||||
|
{
|
||||||
|
BT_DBG("Mute %u", mics_inst.mute);
|
||||||
|
|
||||||
|
return bt_gatt_attr_read(conn, attr, buf, len, offset,
|
||||||
|
&mics_inst.mute, sizeof(mics_inst.mute));
|
||||||
|
}
|
||||||
|
|
||||||
|
static ssize_t write_mute(struct bt_conn *conn, const struct bt_gatt_attr *attr,
|
||||||
|
const void *buf, uint16_t len, uint16_t offset,
|
||||||
|
uint8_t flags)
|
||||||
|
{
|
||||||
|
const uint8_t *val = buf;
|
||||||
|
|
||||||
|
if (offset > 0) {
|
||||||
|
return BT_GATT_ERR(BT_ATT_ERR_INVALID_OFFSET);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (len != sizeof(mics_inst.mute)) {
|
||||||
|
return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((conn != NULL && *val == BT_MICS_MUTE_DISABLED) ||
|
||||||
|
*val > BT_MICS_MUTE_DISABLED) {
|
||||||
|
return BT_GATT_ERR(BT_MICS_ERR_VAL_OUT_OF_RANGE);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (conn != NULL && mics_inst.mute == BT_MICS_MUTE_DISABLED) {
|
||||||
|
return BT_GATT_ERR(BT_MICS_ERR_MUTE_DISABLED);
|
||||||
|
}
|
||||||
|
|
||||||
|
BT_DBG("%u", *val);
|
||||||
|
|
||||||
|
if (*val != mics_inst.mute) {
|
||||||
|
mics_inst.mute = *val;
|
||||||
|
|
||||||
|
bt_gatt_notify_uuid(NULL, BT_UUID_MICS_MUTE,
|
||||||
|
mics_inst.service_p->attrs,
|
||||||
|
&mics_inst.mute, sizeof(mics_inst.mute));
|
||||||
|
|
||||||
|
if (mics_inst.cb != NULL && mics_inst.cb->mute != NULL) {
|
||||||
|
mics_inst.cb->mute(NULL, 0, mics_inst.mute);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return len;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#define DUMMY_INCLUDE(i, _) BT_GATT_INCLUDE_SERVICE(NULL),
|
||||||
|
#define AICS_INCLUDES(cnt) UTIL_LISTIFY(cnt, DUMMY_INCLUDE)
|
||||||
|
|
||||||
|
#define BT_MICS_SERVICE_DEFINITION \
|
||||||
|
BT_GATT_PRIMARY_SERVICE(BT_UUID_MICS), \
|
||||||
|
AICS_INCLUDES(CONFIG_BT_MICS_AICS_INSTANCE_COUNT) \
|
||||||
|
BT_GATT_CHARACTERISTIC(BT_UUID_MICS_MUTE, \
|
||||||
|
BT_GATT_CHRC_READ | BT_GATT_CHRC_WRITE | BT_GATT_CHRC_NOTIFY, \
|
||||||
|
BT_GATT_PERM_READ_ENCRYPT | BT_GATT_PERM_WRITE_ENCRYPT, \
|
||||||
|
read_mute, write_mute, NULL), \
|
||||||
|
BT_GATT_CCC(mute_cfg_changed, \
|
||||||
|
BT_GATT_PERM_READ | BT_GATT_PERM_WRITE_ENCRYPT)
|
||||||
|
|
||||||
|
#define MICS_ATTR_COUNT \
|
||||||
|
ARRAY_SIZE(((struct bt_gatt_attr []){ BT_MICS_SERVICE_DEFINITION }))
|
||||||
|
#define MICS_INCL_COUNT (CONFIG_BT_MICS_AICS_INSTANCE_COUNT)
|
||||||
|
|
||||||
|
static struct bt_gatt_attr mics_attrs[] = { BT_MICS_SERVICE_DEFINITION };
|
||||||
|
static struct bt_gatt_service mics_svc;
|
||||||
|
|
||||||
|
static int prepare_aics_inst(struct bt_mics_register_param *param)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
int j;
|
||||||
|
int err;
|
||||||
|
|
||||||
|
for (j = 0, i = 0; i < ARRAY_SIZE(mics_attrs); i++) {
|
||||||
|
if (bt_uuid_cmp(mics_attrs[i].uuid, BT_UUID_GATT_INCLUDE) == 0) {
|
||||||
|
mics_inst.aics_insts[j] = bt_aics_free_instance_get();
|
||||||
|
if (mics_inst.aics_insts[j] == NULL) {
|
||||||
|
BT_DBG("Could not get free AICS instances[%u]", j);
|
||||||
|
return -ENOMEM;
|
||||||
|
}
|
||||||
|
|
||||||
|
err = bt_aics_register(mics_inst.aics_insts[j],
|
||||||
|
¶m->aics_param[j]);
|
||||||
|
if (err != 0) {
|
||||||
|
BT_DBG("Could not register AICS instance[%u]: %d", j, err);
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
mics_attrs[i].user_data = bt_aics_svc_decl_get(mics_inst.aics_insts[j]);
|
||||||
|
j++;
|
||||||
|
|
||||||
|
if (j == CONFIG_BT_MICS_AICS_INSTANCE_COUNT) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
__ASSERT(j == CONFIG_BT_MICS_AICS_INSTANCE_COUNT,
|
||||||
|
"Invalid AICS instance count");
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/****************************** PUBLIC API ******************************/
|
||||||
|
int bt_mics_register(struct bt_mics_register_param *param)
|
||||||
|
{
|
||||||
|
int err;
|
||||||
|
|
||||||
|
__ASSERT(param, "MICS register parameter cannot be NULL");
|
||||||
|
|
||||||
|
if (CONFIG_BT_MICS_AICS_INSTANCE_COUNT > 0) {
|
||||||
|
prepare_aics_inst(param);
|
||||||
|
}
|
||||||
|
|
||||||
|
mics_svc = (struct bt_gatt_service)BT_GATT_SERVICE(mics_attrs);
|
||||||
|
mics_inst.service_p = &mics_svc;
|
||||||
|
err = bt_gatt_service_register(&mics_svc);
|
||||||
|
|
||||||
|
if (err != 0) {
|
||||||
|
BT_ERR("MICS service register failed: %d", err);
|
||||||
|
}
|
||||||
|
|
||||||
|
mics_inst.cb = param->cb;
|
||||||
|
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
int bt_mics_aics_deactivate(struct bt_aics *inst)
|
||||||
|
{
|
||||||
|
CHECKIF(inst == NULL) {
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (CONFIG_BT_MICS_AICS_INSTANCE_COUNT > 0) {
|
||||||
|
return bt_aics_deactivate(inst);
|
||||||
|
}
|
||||||
|
|
||||||
|
return -EOPNOTSUPP;
|
||||||
|
}
|
||||||
|
|
||||||
|
int bt_mics_aics_activate(struct bt_aics *inst)
|
||||||
|
{
|
||||||
|
CHECKIF(inst == NULL) {
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (CONFIG_BT_MICS_AICS_INSTANCE_COUNT > 0) {
|
||||||
|
return bt_aics_activate(inst);
|
||||||
|
}
|
||||||
|
|
||||||
|
return -EOPNOTSUPP;
|
||||||
|
}
|
||||||
|
|
||||||
|
int bt_mics_mute_disable(void)
|
||||||
|
{
|
||||||
|
uint8_t val = BT_MICS_MUTE_DISABLED;
|
||||||
|
int err = write_mute(NULL, NULL, &val, sizeof(val), 0, 0);
|
||||||
|
|
||||||
|
return err > 0 ? 0 : err;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* CONFIG_BT_MICS */
|
||||||
|
|
||||||
|
static bool valid_aics_inst(struct bt_aics *aics)
|
||||||
|
{
|
||||||
|
if (aics == NULL) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if defined(CONFIG_BT_MICS)
|
||||||
|
for (int i = 0; i < ARRAY_SIZE(mics_inst.aics_insts); i++) {
|
||||||
|
if (mics_inst.aics_insts[i] == aics) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif /* CONFIG_BT_MICS */
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
int bt_mics_get(struct bt_conn *conn, struct bt_mics *service)
|
||||||
|
{
|
||||||
|
CHECKIF(service == NULL) {
|
||||||
|
BT_DBG("NULL service pointer");
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if defined(CONFIG_BT_MICS_CLIENT)
|
||||||
|
if (conn != NULL) {
|
||||||
|
return bt_mics_client_service_get(conn, service);
|
||||||
|
}
|
||||||
|
#endif /* CONFIG_BT_MICS_CLIENT */
|
||||||
|
|
||||||
|
#if defined(CONFIG_BT_MICS)
|
||||||
|
if (conn == NULL) {
|
||||||
|
service->aics_cnt = ARRAY_SIZE(mics_inst.aics_insts);
|
||||||
|
service->aics = mics_inst.aics_insts;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
#endif /* CONFIG_BT_MICS */
|
||||||
|
return -EOPNOTSUPP;
|
||||||
|
}
|
||||||
|
|
||||||
|
int bt_mics_unmute(struct bt_conn *conn)
|
||||||
|
{
|
||||||
|
#if defined(CONFIG_BT_MICS_CLIENT)
|
||||||
|
if (conn != NULL) {
|
||||||
|
return bt_mics_client_unmute(conn);
|
||||||
|
}
|
||||||
|
#endif /* CONFIG_BT_MICS_CLIENT */
|
||||||
|
|
||||||
|
#if defined(CONFIG_BT_MICS)
|
||||||
|
if (conn == NULL) {
|
||||||
|
uint8_t val = BT_MICS_MUTE_UNMUTED;
|
||||||
|
int err = write_mute(NULL, NULL, &val, sizeof(val), 0, 0);
|
||||||
|
|
||||||
|
return err > 0 ? 0 : err;
|
||||||
|
}
|
||||||
|
#endif /* CONFIG_BT_MICS */
|
||||||
|
return -EOPNOTSUPP;
|
||||||
|
}
|
||||||
|
|
||||||
|
int bt_mics_mute(struct bt_conn *conn)
|
||||||
|
{
|
||||||
|
if (conn != NULL) {
|
||||||
|
if (IS_ENABLED(CONFIG_BT_MICS_CLIENT)) {
|
||||||
|
return bt_mics_client_mute(conn);
|
||||||
|
} else {
|
||||||
|
return -EOPNOTSUPP;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#if defined(CONFIG_BT_MICS)
|
||||||
|
uint8_t val = BT_MICS_MUTE_MUTED;
|
||||||
|
int err = write_mute(NULL, NULL, &val, sizeof(val), 0, 0);
|
||||||
|
|
||||||
|
return err > 0 ? 0 : err;
|
||||||
|
#else
|
||||||
|
return -EOPNOTSUPP;
|
||||||
|
#endif /* CONFIG_BT_MICS */
|
||||||
|
}
|
||||||
|
|
||||||
|
int bt_mics_mute_get(struct bt_conn *conn)
|
||||||
|
{
|
||||||
|
if (conn != NULL) {
|
||||||
|
if (IS_ENABLED(CONFIG_BT_MICS_CLIENT)) {
|
||||||
|
return bt_mics_client_mute_get(conn);
|
||||||
|
} else {
|
||||||
|
return -EOPNOTSUPP;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#if defined(CONFIG_BT_MICS)
|
||||||
|
if (mics_inst.cb && mics_inst.cb->mute) {
|
||||||
|
mics_inst.cb->mute(NULL, 0, mics_inst.mute);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
#else
|
||||||
|
return -EOPNOTSUPP;
|
||||||
|
#endif /* CONFIG_BT_MICS */
|
||||||
|
}
|
||||||
|
|
||||||
|
int bt_mics_aics_state_get(struct bt_conn *conn, struct bt_aics *inst)
|
||||||
|
{
|
||||||
|
if (IS_ENABLED(CONFIG_BT_MICS_CLIENT_AICS) &&
|
||||||
|
conn != NULL &&
|
||||||
|
bt_mics_client_valid_aics_inst(conn, inst)) {
|
||||||
|
return bt_aics_state_get(conn, inst);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (IS_ENABLED(CONFIG_BT_MICS_AICS) &&
|
||||||
|
conn == NULL &&
|
||||||
|
valid_aics_inst(inst)) {
|
||||||
|
return bt_aics_state_get(NULL, inst);
|
||||||
|
}
|
||||||
|
|
||||||
|
return -EOPNOTSUPP;
|
||||||
|
}
|
||||||
|
|
||||||
|
int bt_mics_aics_gain_setting_get(struct bt_conn *conn, struct bt_aics *inst)
|
||||||
|
{
|
||||||
|
if (IS_ENABLED(CONFIG_BT_MICS_CLIENT_AICS) &&
|
||||||
|
conn != NULL &&
|
||||||
|
bt_mics_client_valid_aics_inst(conn, inst)) {
|
||||||
|
return bt_aics_gain_setting_get(conn, inst);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (IS_ENABLED(CONFIG_BT_MICS_AICS) &&
|
||||||
|
conn == NULL &&
|
||||||
|
valid_aics_inst(inst)) {
|
||||||
|
return bt_aics_gain_setting_get(NULL, inst);
|
||||||
|
}
|
||||||
|
|
||||||
|
return -EOPNOTSUPP;
|
||||||
|
}
|
||||||
|
|
||||||
|
int bt_mics_aics_type_get(struct bt_conn *conn, struct bt_aics *inst)
|
||||||
|
{
|
||||||
|
if (IS_ENABLED(CONFIG_BT_MICS_CLIENT_AICS) &&
|
||||||
|
conn != NULL &&
|
||||||
|
bt_mics_client_valid_aics_inst(conn, inst)) {
|
||||||
|
return bt_aics_type_get(conn, inst);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (IS_ENABLED(CONFIG_BT_MICS_AICS) &&
|
||||||
|
conn == NULL &&
|
||||||
|
valid_aics_inst(inst)) {
|
||||||
|
return bt_aics_type_get(NULL, inst);
|
||||||
|
}
|
||||||
|
|
||||||
|
return -EOPNOTSUPP;
|
||||||
|
}
|
||||||
|
|
||||||
|
int bt_mics_aics_status_get(struct bt_conn *conn, struct bt_aics *inst)
|
||||||
|
{
|
||||||
|
if (IS_ENABLED(CONFIG_BT_MICS_CLIENT_AICS) &&
|
||||||
|
conn != NULL &&
|
||||||
|
bt_mics_client_valid_aics_inst(conn, inst)) {
|
||||||
|
return bt_aics_status_get(conn, inst);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (IS_ENABLED(CONFIG_BT_MICS_AICS) &&
|
||||||
|
conn == NULL &&
|
||||||
|
valid_aics_inst(inst)) {
|
||||||
|
return bt_aics_status_get(NULL, inst);
|
||||||
|
}
|
||||||
|
|
||||||
|
return -EOPNOTSUPP;
|
||||||
|
}
|
||||||
|
int bt_mics_aics_unmute(struct bt_conn *conn, struct bt_aics *inst)
|
||||||
|
{
|
||||||
|
if (IS_ENABLED(CONFIG_BT_MICS_CLIENT_AICS) &&
|
||||||
|
conn != NULL &&
|
||||||
|
bt_mics_client_valid_aics_inst(conn, inst)) {
|
||||||
|
return bt_aics_unmute(conn, inst);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (IS_ENABLED(CONFIG_BT_MICS_AICS) &&
|
||||||
|
conn == NULL &&
|
||||||
|
valid_aics_inst(inst)) {
|
||||||
|
return bt_aics_unmute(NULL, inst);
|
||||||
|
}
|
||||||
|
|
||||||
|
return -EOPNOTSUPP;
|
||||||
|
}
|
||||||
|
|
||||||
|
int bt_mics_aics_mute(struct bt_conn *conn, struct bt_aics *inst)
|
||||||
|
{
|
||||||
|
if (IS_ENABLED(CONFIG_BT_MICS_CLIENT_AICS) &&
|
||||||
|
conn != NULL &&
|
||||||
|
bt_mics_client_valid_aics_inst(conn, inst)) {
|
||||||
|
return bt_aics_mute(conn, inst);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (IS_ENABLED(CONFIG_BT_MICS_AICS) &&
|
||||||
|
conn == NULL &&
|
||||||
|
valid_aics_inst(inst)) {
|
||||||
|
return bt_aics_mute(NULL, inst);
|
||||||
|
}
|
||||||
|
|
||||||
|
return -EOPNOTSUPP;
|
||||||
|
}
|
||||||
|
|
||||||
|
int bt_mics_aics_manual_gain_set(struct bt_conn *conn, struct bt_aics *inst)
|
||||||
|
{
|
||||||
|
if (IS_ENABLED(CONFIG_BT_MICS_CLIENT_AICS) &&
|
||||||
|
conn != NULL &&
|
||||||
|
bt_mics_client_valid_aics_inst(conn, inst)) {
|
||||||
|
return bt_aics_manual_gain_set(conn, inst);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (IS_ENABLED(CONFIG_BT_MICS_AICS) &&
|
||||||
|
conn == NULL &&
|
||||||
|
valid_aics_inst(inst)) {
|
||||||
|
return bt_aics_manual_gain_set(NULL, inst);
|
||||||
|
}
|
||||||
|
|
||||||
|
return -EOPNOTSUPP;
|
||||||
|
}
|
||||||
|
|
||||||
|
int bt_mics_aics_automatic_gain_set(struct bt_conn *conn, struct bt_aics *inst)
|
||||||
|
{
|
||||||
|
if (IS_ENABLED(CONFIG_BT_MICS_CLIENT_AICS) &&
|
||||||
|
conn != NULL &&
|
||||||
|
bt_mics_client_valid_aics_inst(conn, inst)) {
|
||||||
|
return bt_aics_automatic_gain_set(conn, inst);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (IS_ENABLED(CONFIG_BT_MICS_AICS) &&
|
||||||
|
conn == NULL &&
|
||||||
|
valid_aics_inst(inst)) {
|
||||||
|
return bt_aics_automatic_gain_set(NULL, inst);
|
||||||
|
}
|
||||||
|
|
||||||
|
return -EOPNOTSUPP;
|
||||||
|
}
|
||||||
|
|
||||||
|
int bt_mics_aics_gain_set(struct bt_conn *conn, struct bt_aics *inst,
|
||||||
|
int8_t gain)
|
||||||
|
{
|
||||||
|
if (IS_ENABLED(CONFIG_BT_MICS_CLIENT_AICS) &&
|
||||||
|
conn != NULL &&
|
||||||
|
bt_mics_client_valid_aics_inst(conn, inst)) {
|
||||||
|
return bt_aics_gain_set(conn, inst, gain);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (IS_ENABLED(CONFIG_BT_MICS_AICS) &&
|
||||||
|
conn == NULL &&
|
||||||
|
valid_aics_inst(inst)) {
|
||||||
|
return bt_aics_gain_set(NULL, inst, gain);
|
||||||
|
}
|
||||||
|
|
||||||
|
return -EOPNOTSUPP;
|
||||||
|
}
|
||||||
|
|
||||||
|
int bt_mics_aics_description_get(struct bt_conn *conn, struct bt_aics *inst)
|
||||||
|
{
|
||||||
|
if (IS_ENABLED(CONFIG_BT_MICS_CLIENT_AICS) &&
|
||||||
|
conn != NULL &&
|
||||||
|
bt_mics_client_valid_aics_inst(conn, inst)) {
|
||||||
|
return bt_aics_description_get(conn, inst);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (IS_ENABLED(CONFIG_BT_MICS_AICS) &&
|
||||||
|
conn == NULL &&
|
||||||
|
valid_aics_inst(inst)) {
|
||||||
|
return bt_aics_description_get(NULL, inst);
|
||||||
|
}
|
||||||
|
|
||||||
|
return -EOPNOTSUPP;
|
||||||
|
}
|
||||||
|
|
||||||
|
int bt_mics_aics_description_set(struct bt_conn *conn, struct bt_aics *inst,
|
||||||
|
const char *description)
|
||||||
|
{
|
||||||
|
if (IS_ENABLED(CONFIG_BT_MICS_CLIENT_AICS) &&
|
||||||
|
conn != NULL &&
|
||||||
|
bt_mics_client_valid_aics_inst(conn, inst)) {
|
||||||
|
return bt_aics_description_set(conn, inst, description);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (IS_ENABLED(CONFIG_BT_MICS_AICS) &&
|
||||||
|
conn == NULL &&
|
||||||
|
valid_aics_inst(inst)) {
|
||||||
|
return bt_aics_description_set(NULL, inst, description);
|
||||||
|
}
|
||||||
|
|
||||||
|
return -EOPNOTSUPP;
|
||||||
|
}
|
544
subsys/bluetooth/audio/mics_client.c
Normal file
544
subsys/bluetooth/audio/mics_client.c
Normal file
|
@ -0,0 +1,544 @@
|
||||||
|
/* Bluetooth MICS client - Microphone Control Profile - 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/mics.h>
|
||||||
|
|
||||||
|
#define BT_DBG_ENABLED IS_ENABLED(CONFIG_BT_DEBUG_MICS_CLIENT)
|
||||||
|
#define LOG_MODULE_NAME bt_mics_client
|
||||||
|
#include "common/log.h"
|
||||||
|
|
||||||
|
struct mics_instance {
|
||||||
|
uint16_t start_handle;
|
||||||
|
uint16_t end_handle;
|
||||||
|
uint16_t mute_handle;
|
||||||
|
struct bt_gatt_subscribe_params mute_sub_params;
|
||||||
|
struct bt_gatt_discover_params mute_sub_disc_params;
|
||||||
|
|
||||||
|
bool busy;
|
||||||
|
uint8_t mute_val_buf[1]; /* Mute value is a single octet */
|
||||||
|
struct bt_gatt_write_params write_params;
|
||||||
|
struct bt_gatt_read_params read_params;
|
||||||
|
struct bt_gatt_discover_params discover_params;
|
||||||
|
|
||||||
|
uint8_t aics_inst_cnt;
|
||||||
|
struct bt_aics *aics[CONFIG_BT_MICS_CLIENT_MAX_AICS_INST];
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Callback functions */
|
||||||
|
static struct bt_mics_cb *mics_client_cb;
|
||||||
|
|
||||||
|
static struct mics_instance mics_insts[CONFIG_BT_MAX_CONN];
|
||||||
|
static struct bt_uuid *mics_uuid = BT_UUID_MICS;
|
||||||
|
|
||||||
|
bool bt_mics_client_valid_aics_inst(struct bt_conn *conn, struct bt_aics *aics)
|
||||||
|
{
|
||||||
|
uint8_t conn_index;
|
||||||
|
|
||||||
|
CHECKIF(conn == NULL) {
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (aics == NULL) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
conn_index = bt_conn_index(conn);
|
||||||
|
|
||||||
|
for (int i = 0; i < ARRAY_SIZE(mics_insts[conn_index].aics); i++) {
|
||||||
|
if (mics_insts[conn_index].aics[i] == aics) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint8_t mute_notify_handler(struct bt_conn *conn,
|
||||||
|
struct bt_gatt_subscribe_params *params,
|
||||||
|
const void *data, uint16_t length)
|
||||||
|
{
|
||||||
|
uint8_t *mute_val;
|
||||||
|
|
||||||
|
if (data != NULL) {
|
||||||
|
if (length == sizeof(*mute_val)) {
|
||||||
|
mute_val = (uint8_t *)data;
|
||||||
|
BT_DBG("Mute %u", *mute_val);
|
||||||
|
if (mics_client_cb != NULL &&
|
||||||
|
mics_client_cb->mute != NULL) {
|
||||||
|
mics_client_cb->mute(conn, 0, *mute_val);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
BT_DBG("Invalid length %u (expected %zu)",
|
||||||
|
length, sizeof(*mute_val));
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return BT_GATT_ITER_CONTINUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint8_t mics_client_read_mute_cb(struct bt_conn *conn, uint8_t err,
|
||||||
|
struct bt_gatt_read_params *params,
|
||||||
|
const void *data, uint16_t length)
|
||||||
|
{
|
||||||
|
uint8_t cb_err = err;
|
||||||
|
uint8_t *mute_val = NULL;
|
||||||
|
struct mics_instance *mics_inst = &mics_insts[bt_conn_index(conn)];
|
||||||
|
|
||||||
|
mics_inst->busy = false;
|
||||||
|
|
||||||
|
if (err > 0) {
|
||||||
|
BT_DBG("err: 0x%02X", err);
|
||||||
|
} else if (data != NULL) {
|
||||||
|
if (length == sizeof(*mute_val)) {
|
||||||
|
mute_val = (uint8_t *)data;
|
||||||
|
BT_DBG("Mute %u", *mute_val);
|
||||||
|
} else {
|
||||||
|
BT_DBG("Invalid length %u (expected %zu)",
|
||||||
|
length, sizeof(*mute_val));
|
||||||
|
cb_err = BT_ATT_ERR_INVALID_ATTRIBUTE_LEN;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mics_client_cb != NULL && mics_client_cb->mute != NULL) {
|
||||||
|
mics_client_cb->mute(conn, cb_err, *mute_val);
|
||||||
|
}
|
||||||
|
|
||||||
|
return BT_GATT_ITER_STOP;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void mics_client_write_mics_mute_cb(struct bt_conn *conn, uint8_t err,
|
||||||
|
struct bt_gatt_write_params *params)
|
||||||
|
{
|
||||||
|
struct mics_instance *mics_inst = &mics_insts[bt_conn_index(conn)];
|
||||||
|
uint8_t mute_val = mics_inst->mute_val_buf[0];
|
||||||
|
|
||||||
|
BT_DBG("Write %s (0x%02X)", err ? "failed" : "successful", err);
|
||||||
|
|
||||||
|
mics_inst->busy = false;
|
||||||
|
|
||||||
|
if (mute_val == BT_MICS_MUTE_UNMUTED) {
|
||||||
|
if (mics_client_cb != NULL &&
|
||||||
|
mics_client_cb->unmute_write != NULL) {
|
||||||
|
mics_client_cb->unmute_write(conn, err);
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
if (mics_client_cb != NULL &&
|
||||||
|
mics_client_cb->mute_write != NULL) {
|
||||||
|
mics_client_cb->mute_write(conn, err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void aics_discover_cb(struct bt_conn *conn, struct bt_aics *inst,
|
||||||
|
int err)
|
||||||
|
{
|
||||||
|
struct mics_instance *mics_inst = &mics_insts[bt_conn_index(conn)];
|
||||||
|
|
||||||
|
if (err == 0) {
|
||||||
|
/* Continue discovery of included services */
|
||||||
|
err = bt_gatt_discover(conn, &mics_inst->discover_params);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (err != 0) {
|
||||||
|
BT_DBG("Discover failed (err %d)", err);
|
||||||
|
if (mics_client_cb != NULL &&
|
||||||
|
mics_client_cb->discover != NULL) {
|
||||||
|
mics_client_cb->discover(conn, err, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint8_t mics_discover_include_func(
|
||||||
|
struct bt_conn *conn, const struct bt_gatt_attr *attr,
|
||||||
|
struct bt_gatt_discover_params *params)
|
||||||
|
{
|
||||||
|
struct mics_instance *mics_inst = &mics_insts[bt_conn_index(conn)];
|
||||||
|
|
||||||
|
if (attr == NULL) {
|
||||||
|
BT_DBG("Discover include complete for MICS: %u AICS",
|
||||||
|
mics_inst->aics_inst_cnt);
|
||||||
|
(void)memset(params, 0, sizeof(*params));
|
||||||
|
|
||||||
|
if (mics_client_cb != NULL &&
|
||||||
|
mics_client_cb->discover != NULL) {
|
||||||
|
mics_client_cb->discover(conn, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
return BT_GATT_ITER_STOP;
|
||||||
|
}
|
||||||
|
|
||||||
|
BT_DBG("[ATTRIBUTE] handle 0x%04X", attr->handle);
|
||||||
|
|
||||||
|
if (params->type == BT_GATT_DISCOVER_INCLUDE) {
|
||||||
|
struct bt_gatt_include *include = (struct bt_gatt_include *)attr->user_data;
|
||||||
|
|
||||||
|
BT_DBG("Include UUID %s", bt_uuid_str(include->uuid));
|
||||||
|
|
||||||
|
if (bt_uuid_cmp(include->uuid, BT_UUID_AICS) == 0 &&
|
||||||
|
mics_inst->aics_inst_cnt < CONFIG_BT_MICS_CLIENT_MAX_AICS_INST) {
|
||||||
|
uint8_t inst_idx;
|
||||||
|
int err;
|
||||||
|
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
|
||||||
|
*/
|
||||||
|
mics_inst->discover_params.start_handle = attr->handle + 1;
|
||||||
|
|
||||||
|
inst_idx = mics_inst->aics_inst_cnt++;
|
||||||
|
err = bt_aics_discover(conn, mics_inst->aics[inst_idx],
|
||||||
|
¶m);
|
||||||
|
if (err != 0) {
|
||||||
|
BT_DBG("AICS Discover failed (err %d)", err);
|
||||||
|
if (mics_client_cb != NULL &&
|
||||||
|
mics_client_cb->discover != NULL) {
|
||||||
|
mics_client_cb->discover(conn, err, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return BT_GATT_ITER_STOP;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 mics_discover_func(struct bt_conn *conn,
|
||||||
|
const struct bt_gatt_attr *attr,
|
||||||
|
struct bt_gatt_discover_params *params)
|
||||||
|
{
|
||||||
|
struct mics_instance *mics_inst = &mics_insts[bt_conn_index(conn)];
|
||||||
|
|
||||||
|
if (attr == NULL) {
|
||||||
|
int err = 0;
|
||||||
|
|
||||||
|
BT_DBG("Setup complete for MICS");
|
||||||
|
(void)memset(params, 0, sizeof(*params));
|
||||||
|
if (CONFIG_BT_MICS_CLIENT_MAX_AICS_INST > 0) {
|
||||||
|
/* Discover included services */
|
||||||
|
mics_inst->discover_params.start_handle = mics_inst->start_handle;
|
||||||
|
mics_inst->discover_params.end_handle = mics_inst->end_handle;
|
||||||
|
mics_inst->discover_params.type = BT_GATT_DISCOVER_INCLUDE;
|
||||||
|
mics_inst->discover_params.func = mics_discover_include_func;
|
||||||
|
|
||||||
|
err = bt_gatt_discover(conn,
|
||||||
|
&mics_inst->discover_params);
|
||||||
|
if (err != 0) {
|
||||||
|
BT_DBG("Discover failed (err %d)", err);
|
||||||
|
if (mics_client_cb != NULL &&
|
||||||
|
mics_client_cb->discover != NULL) {
|
||||||
|
mics_client_cb->discover(conn, err, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (mics_client_cb != NULL &&
|
||||||
|
mics_client_cb->discover != NULL) {
|
||||||
|
mics_client_cb->discover(conn, err, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return BT_GATT_ITER_STOP;
|
||||||
|
}
|
||||||
|
|
||||||
|
BT_DBG("[ATTRIBUTE] handle 0x%04X", attr->handle);
|
||||||
|
|
||||||
|
if (params->type == BT_GATT_DISCOVER_CHARACTERISTIC) {
|
||||||
|
struct bt_gatt_chrc *chrc = (struct bt_gatt_chrc *)attr->user_data;
|
||||||
|
struct bt_gatt_subscribe_params *sub_params = NULL;
|
||||||
|
|
||||||
|
if (bt_uuid_cmp(chrc->uuid, BT_UUID_MICS_MUTE) == 0) {
|
||||||
|
BT_DBG("Mute");
|
||||||
|
mics_inst->mute_handle = chrc->value_handle;
|
||||||
|
sub_params = &mics_inst->mute_sub_params;
|
||||||
|
sub_params->disc_params = &mics_inst->mute_sub_disc_params;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sub_params != NULL) {
|
||||||
|
int err;
|
||||||
|
|
||||||
|
/* With ccc_handle == 0 it will use auto discovery */
|
||||||
|
sub_params->ccc_handle = 0;
|
||||||
|
sub_params->end_handle = mics_inst->end_handle;
|
||||||
|
sub_params->value = BT_GATT_CCC_NOTIFY;
|
||||||
|
sub_params->value_handle = chrc->value_handle;
|
||||||
|
sub_params->notify = mute_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: %d",
|
||||||
|
attr->handle, err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return BT_GATT_ITER_CONTINUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint8_t primary_discover_func(struct bt_conn *conn,
|
||||||
|
const struct bt_gatt_attr *attr,
|
||||||
|
struct bt_gatt_discover_params *params)
|
||||||
|
{
|
||||||
|
struct mics_instance *mics_inst = &mics_insts[bt_conn_index(conn)];
|
||||||
|
|
||||||
|
if (attr == NULL) {
|
||||||
|
BT_DBG("Could not find a MICS instance on the server");
|
||||||
|
if (mics_client_cb != NULL &&
|
||||||
|
mics_client_cb->discover != NULL) {
|
||||||
|
mics_client_cb->discover(conn, -ENODATA, 0);
|
||||||
|
}
|
||||||
|
return BT_GATT_ITER_STOP;
|
||||||
|
}
|
||||||
|
|
||||||
|
BT_DBG("[ATTRIBUTE] handle 0x%04X", attr->handle);
|
||||||
|
|
||||||
|
if (params->type == BT_GATT_DISCOVER_PRIMARY) {
|
||||||
|
struct bt_gatt_service_val *prim_service =
|
||||||
|
(struct bt_gatt_service_val *)attr->user_data;
|
||||||
|
int err;
|
||||||
|
|
||||||
|
BT_DBG("Primary discover complete");
|
||||||
|
mics_inst->start_handle = attr->handle + 1;
|
||||||
|
mics_inst->end_handle = prim_service->end_handle;
|
||||||
|
|
||||||
|
/* Discover characteristics */
|
||||||
|
mics_inst->discover_params.uuid = NULL;
|
||||||
|
mics_inst->discover_params.start_handle = mics_inst->start_handle;
|
||||||
|
mics_inst->discover_params.end_handle = mics_inst->end_handle;
|
||||||
|
mics_inst->discover_params.type = BT_GATT_DISCOVER_CHARACTERISTIC;
|
||||||
|
mics_inst->discover_params.func = mics_discover_func;
|
||||||
|
|
||||||
|
err = bt_gatt_discover(conn, &mics_inst->discover_params);
|
||||||
|
if (err != 0) {
|
||||||
|
BT_DBG("Discover failed (err %d)", err);
|
||||||
|
if (mics_client_cb != NULL &&
|
||||||
|
mics_client_cb->discover != NULL) {
|
||||||
|
mics_client_cb->discover(conn, err, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return BT_GATT_ITER_STOP;
|
||||||
|
}
|
||||||
|
|
||||||
|
return BT_GATT_ITER_CONTINUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void mics_client_reset(struct bt_conn *conn)
|
||||||
|
{
|
||||||
|
struct mics_instance *mics_inst = &mics_insts[bt_conn_index(conn)];
|
||||||
|
|
||||||
|
mics_inst->start_handle = 0;
|
||||||
|
mics_inst->end_handle = 0;
|
||||||
|
mics_inst->mute_handle = 0;
|
||||||
|
mics_inst->aics_inst_cnt = 0;
|
||||||
|
|
||||||
|
/* It's okay if this fails */
|
||||||
|
(void)bt_gatt_unsubscribe(conn, &mics_inst->mute_sub_params);
|
||||||
|
}
|
||||||
|
|
||||||
|
int bt_mics_discover(struct bt_conn *conn)
|
||||||
|
{
|
||||||
|
static bool initialized;
|
||||||
|
struct mics_instance *mics_inst;
|
||||||
|
/*
|
||||||
|
* This will initiate a discover procedure. The procedure will do the
|
||||||
|
* following sequence:
|
||||||
|
* 1) Primary discover for the MICS
|
||||||
|
* 2) Characteristic discover of the MICS
|
||||||
|
* 3) Discover services included in MICS (AICS)
|
||||||
|
* 4) For each included service found; discovery of the characteristiscs
|
||||||
|
* 5) When everything above have been discovered, the callback is called
|
||||||
|
*/
|
||||||
|
|
||||||
|
CHECKIF(conn == NULL) {
|
||||||
|
BT_DBG("NULL conn");
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
mics_inst = &mics_insts[bt_conn_index(conn)];
|
||||||
|
|
||||||
|
(void)memset(&mics_inst->discover_params, 0,
|
||||||
|
sizeof(mics_inst->discover_params));
|
||||||
|
mics_client_reset(conn);
|
||||||
|
|
||||||
|
if (IS_ENABLED(CONFIG_BT_AICS_CLIENT) &&
|
||||||
|
CONFIG_BT_MICS_CLIENT_MAX_AICS_INST > 0) {
|
||||||
|
for (int i = 0; i < ARRAY_SIZE(mics_inst->aics); i++) {
|
||||||
|
if (!initialized) {
|
||||||
|
mics_inst->aics[i] = bt_aics_client_free_instance_get();
|
||||||
|
|
||||||
|
if (mics_inst->aics[i] == NULL) {
|
||||||
|
return -ENOMEM;
|
||||||
|
}
|
||||||
|
|
||||||
|
bt_aics_client_cb_register(mics_inst->aics[i],
|
||||||
|
&mics_client_cb->aics_cb);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mics_inst->discover_params.func = primary_discover_func;
|
||||||
|
mics_inst->discover_params.uuid = mics_uuid;
|
||||||
|
mics_inst->discover_params.type = BT_GATT_DISCOVER_PRIMARY;
|
||||||
|
mics_inst->discover_params.start_handle = BT_ATT_FIRST_ATTTRIBUTE_HANDLE;
|
||||||
|
mics_inst->discover_params.end_handle = BT_ATT_LAST_ATTTRIBUTE_HANDLE;
|
||||||
|
|
||||||
|
initialized = true;
|
||||||
|
|
||||||
|
return bt_gatt_discover(conn, &mics_inst->discover_params);
|
||||||
|
}
|
||||||
|
|
||||||
|
int bt_mics_client_cb_register(struct bt_mics_cb *cb)
|
||||||
|
{
|
||||||
|
int i, j;
|
||||||
|
|
||||||
|
if (IS_ENABLED(CONFIG_BT_AICS_CLIENT)) {
|
||||||
|
struct bt_aics_cb *aics_cb = NULL;
|
||||||
|
|
||||||
|
if (cb != NULL) {
|
||||||
|
CHECKIF(cb->aics_cb.discover != NULL) {
|
||||||
|
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(mics_insts); i++) {
|
||||||
|
for (j = 0; j < ARRAY_SIZE(mics_insts[i].aics); j++) {
|
||||||
|
if (mics_insts[i].aics[j] != NULL) {
|
||||||
|
bt_aics_client_cb_register(mics_insts[i].aics[j],
|
||||||
|
aics_cb);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mics_client_cb = cb;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int bt_mics_client_service_get(struct bt_conn *conn, struct bt_mics *service)
|
||||||
|
{
|
||||||
|
struct mics_instance *mics_inst;
|
||||||
|
|
||||||
|
CHECKIF(conn == NULL) {
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
CHECKIF(service == NULL) {
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
mics_inst = &mics_insts[bt_conn_index(conn)];
|
||||||
|
|
||||||
|
service->aics_cnt = mics_inst->aics_inst_cnt;
|
||||||
|
service->aics = mics_inst->aics;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int bt_mics_client_mute_get(struct bt_conn *conn)
|
||||||
|
{
|
||||||
|
int err;
|
||||||
|
struct mics_instance *mics_inst;
|
||||||
|
|
||||||
|
CHECKIF(conn == NULL) {
|
||||||
|
BT_DBG("NULL conn");
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
mics_inst = &mics_insts[bt_conn_index(conn)];
|
||||||
|
|
||||||
|
if (mics_inst->mute_handle == 0) {
|
||||||
|
BT_DBG("Handle not set");
|
||||||
|
return -EINVAL;
|
||||||
|
} else if (mics_inst->busy) {
|
||||||
|
return -EBUSY;
|
||||||
|
}
|
||||||
|
|
||||||
|
mics_inst->read_params.func = mics_client_read_mute_cb;
|
||||||
|
mics_inst->read_params.handle_count = 1;
|
||||||
|
mics_inst->read_params.single.handle = mics_inst->mute_handle;
|
||||||
|
mics_inst->read_params.single.offset = 0U;
|
||||||
|
|
||||||
|
err = bt_gatt_read(conn, &mics_inst->read_params);
|
||||||
|
if (err == 0) {
|
||||||
|
mics_inst->busy = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
int bt_mics_client_write_mute(struct bt_conn *conn, bool mute)
|
||||||
|
{
|
||||||
|
int err;
|
||||||
|
struct mics_instance *mics_inst;
|
||||||
|
|
||||||
|
CHECKIF(conn == NULL) {
|
||||||
|
BT_DBG("NULL conn");
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
mics_inst = &mics_insts[bt_conn_index(conn)];
|
||||||
|
|
||||||
|
if (mics_inst->mute_handle == 0) {
|
||||||
|
BT_DBG("Handle not set");
|
||||||
|
return -EINVAL;
|
||||||
|
} else if (mics_inst->busy) {
|
||||||
|
return -EBUSY;
|
||||||
|
}
|
||||||
|
|
||||||
|
mics_inst->mute_val_buf[0] = mute;
|
||||||
|
mics_inst->write_params.offset = 0;
|
||||||
|
mics_inst->write_params.data = mics_inst->mute_val_buf;
|
||||||
|
mics_inst->write_params.length = sizeof(mute);
|
||||||
|
mics_inst->write_params.handle = mics_inst->mute_handle;
|
||||||
|
mics_inst->write_params.func = mics_client_write_mics_mute_cb;
|
||||||
|
|
||||||
|
err = bt_gatt_write(conn, &mics_inst->write_params);
|
||||||
|
if (err == 0) {
|
||||||
|
mics_inst->busy = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
int bt_mics_client_mute(struct bt_conn *conn)
|
||||||
|
{
|
||||||
|
return bt_mics_client_write_mute(conn, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
int bt_mics_client_unmute(struct bt_conn *conn)
|
||||||
|
{
|
||||||
|
return bt_mics_client_write_mute(conn, false);
|
||||||
|
}
|
18
subsys/bluetooth/audio/mics_internal.h
Normal file
18
subsys/bluetooth/audio/mics_internal.h
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2020-2021 Nordic Semiconductor ASA
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef ZEPHYR_INCLUDE_BLUETOOTH_AUDIO_MICS_INTERNAL_
|
||||||
|
#define ZEPHYR_INCLUDE_BLUETOOTH_AUDIO_MICS_INTERNAL_
|
||||||
|
#include <zephyr/types.h>
|
||||||
|
#include <bluetooth/gatt.h>
|
||||||
|
|
||||||
|
int bt_mics_client_service_get(struct bt_conn *conn, struct bt_mics *service);
|
||||||
|
int bt_mics_client_mute_get(struct bt_conn *conn);
|
||||||
|
int bt_mics_client_mute(struct bt_conn *conn);
|
||||||
|
int bt_mics_client_unmute(struct bt_conn *conn);
|
||||||
|
bool bt_mics_client_valid_aics_inst(struct bt_conn *conn, struct bt_aics *aics);
|
||||||
|
|
||||||
|
#endif /* ZEPHYR_INCLUDE_BLUETOOTH_AUDIO_MICS_INTERNAL_ */
|
|
@ -54,8 +54,8 @@ CONFIG_BT_ISO_TX_BUF_COUNT=5
|
||||||
CONFIG_BT_VOCS_MAX_INSTANCE_COUNT=1
|
CONFIG_BT_VOCS_MAX_INSTANCE_COUNT=1
|
||||||
CONFIG_BT_VOCS_CLIENT_MAX_INSTANCE_COUNT=1
|
CONFIG_BT_VOCS_CLIENT_MAX_INSTANCE_COUNT=1
|
||||||
|
|
||||||
CONFIG_BT_AICS_MAX_INSTANCE_COUNT=1
|
CONFIG_BT_AICS_MAX_INSTANCE_COUNT=2
|
||||||
CONFIG_BT_AICS_CLIENT_MAX_INSTANCE_COUNT=1
|
CONFIG_BT_AICS_CLIENT_MAX_INSTANCE_COUNT=2
|
||||||
|
|
||||||
CONFIG_BT_VCS=y
|
CONFIG_BT_VCS=y
|
||||||
CONFIG_BT_VCS_VOCS_INSTANCE_COUNT=1
|
CONFIG_BT_VCS_VOCS_INSTANCE_COUNT=1
|
||||||
|
@ -63,3 +63,8 @@ CONFIG_BT_VCS_AICS_INSTANCE_COUNT=1
|
||||||
CONFIG_BT_VCS_CLIENT=y
|
CONFIG_BT_VCS_CLIENT=y
|
||||||
CONFIG_BT_VCS_CLIENT_MAX_VOCS_INST=1
|
CONFIG_BT_VCS_CLIENT_MAX_VOCS_INST=1
|
||||||
CONFIG_BT_VCS_CLIENT_MAX_AICS_INST=1
|
CONFIG_BT_VCS_CLIENT_MAX_AICS_INST=1
|
||||||
|
|
||||||
|
CONFIG_BT_MICS=y
|
||||||
|
CONFIG_BT_MICS_AICS_INSTANCE_COUNT=1
|
||||||
|
CONFIG_BT_MICS_CLIENT=y
|
||||||
|
CONFIG_BT_MICS_CLIENT_MAX_AICS_INST=1
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue