Bluetooth: Audio: Audio Input Control Service and client
This commit implements the secondary service Audio Input Control Service (AICS) server and client. Signed-off-by: Emil Gydesen <emil.gydesen@nordicsemi.no>
This commit is contained in:
parent
74a5ed7a23
commit
2016509756
9 changed files with 2485 additions and 2 deletions
451
include/bluetooth/audio/aics.h
Normal file
451
include/bluetooth/audio/aics.h
Normal file
|
@ -0,0 +1,451 @@
|
|||
/*
|
||||
* Copyright (c) 2020 Nordic Semiconductor ASA
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#ifndef ZEPHYR_INCLUDE_BLUETOOTH_SERVICES_AICS_H_
|
||||
#define ZEPHYR_INCLUDE_BLUETOOTH_SERVICES_AICS_H_
|
||||
|
||||
/**
|
||||
* @brief Audio Input Control Service (AICS)
|
||||
*
|
||||
* @defgroup bt_gatt_aics Audio Input Control Service (AICS)
|
||||
*
|
||||
* @ingroup bluetooth
|
||||
* @{
|
||||
*
|
||||
* The Audio Input Control Service is a secondary service, and as such should not be used on its
|
||||
* own, but rather in the context of another (primary) service.
|
||||
*
|
||||
* This API implements both the server and client functionality.
|
||||
* Note that the API abstracts away the change counter in the audio input control state and will
|
||||
* automatically handle any changes to that. If out of date, the client implementation will
|
||||
* autonomously read the change counter value when executing a write request.
|
||||
*
|
||||
* [Experimental] Users should note that the APIs can change as a part of ongoing development.
|
||||
*/
|
||||
|
||||
#include <zephyr/types.h>
|
||||
#include <bluetooth/bluetooth.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/** Audio Input Control Service mute states */
|
||||
#define BT_AICS_STATE_UNMUTED 0x00
|
||||
#define BT_AICS_STATE_MUTED 0x01
|
||||
#define BT_AICS_STATE_MUTE_DISABLED 0x02
|
||||
|
||||
/** Audio Input Control Service input modes */
|
||||
#define BT_AICS_MODE_MANUAL_ONLY 0x00
|
||||
#define BT_AICS_MODE_AUTO_ONLY 0x01
|
||||
#define BT_AICS_MODE_MANUAL 0x02
|
||||
#define BT_AICS_MODE_AUTO 0x03
|
||||
|
||||
/** Audio Input Control Service input types */
|
||||
#define BT_AICS_INPUT_TYPE_UNSPECIFIED 0x00
|
||||
#define BT_AICS_INPUT_TYPE_BLUETOOTH 0x01
|
||||
#define BT_AICS_INPUT_TYPE_MICROPHONE 0x02
|
||||
#define BT_AICS_INPUT_TYPE_ANALOG 0x03
|
||||
#define BT_AICS_INPUT_TYPE_DIGITAL 0x04
|
||||
#define BT_AICS_INPUT_TYPE_RADIO 0x05
|
||||
#define BT_AICS_INPUT_TYPE_STREAMING 0x06
|
||||
|
||||
/** Audio Input Control Service Error codes */
|
||||
#define BT_AICS_ERR_INVALID_COUNTER 0x80
|
||||
#define BT_AICS_ERR_OP_NOT_SUPPORTED 0x81
|
||||
#define BT_AICS_ERR_MUTE_DISABLED 0x82
|
||||
#define BT_AICS_ERR_OUT_OF_RANGE 0x83
|
||||
#define BT_AICS_ERR_GAIN_MODE_NOT_ALLOWED 0x84
|
||||
|
||||
/** @brief Opaque Audio Input Control Service instance. */
|
||||
struct bt_aics;
|
||||
|
||||
/** @brief Structure for initializing a Audio Input Control Service instance. */
|
||||
struct bt_aics_register_param {
|
||||
/** Initial audio input gain (-128 to 127) */
|
||||
int8_t gain;
|
||||
|
||||
/** Initial audio input mute state */
|
||||
uint8_t mute;
|
||||
|
||||
/** Initial audio input mode */
|
||||
uint8_t gain_mode;
|
||||
|
||||
/** Initial audio input gain units (N * 0.1 dB) */
|
||||
uint8_t units;
|
||||
|
||||
/** Initial audio input minimum gain */
|
||||
int8_t min_gain;
|
||||
|
||||
/** Initial audio input maximum gain */
|
||||
int8_t max_gain;
|
||||
|
||||
/** Initial audio input type */
|
||||
uint8_t type;
|
||||
|
||||
/** Initial audio input status (active/inactive) */
|
||||
bool status;
|
||||
|
||||
/** Boolean to set whether the description is writable by clients */
|
||||
bool desc_writable;
|
||||
|
||||
/** Initial audio input description */
|
||||
char *description;
|
||||
|
||||
/** Pointer to the callback structure. */
|
||||
struct bt_aics_cb *cb;
|
||||
};
|
||||
|
||||
/** @brief Structure for discovering a Audio Input Control Service instance. */
|
||||
struct bt_aics_discover_param {
|
||||
/** @brief The start handle of the discovering.
|
||||
*
|
||||
* Typically the @p start_handle of a @ref bt_gatt_include.
|
||||
*/
|
||||
uint16_t start_handle;
|
||||
/** @brief The end handle of the discovering.
|
||||
*
|
||||
* Typically the @p end_handle of a @ref bt_gatt_include.
|
||||
*/
|
||||
uint16_t end_handle;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Get a free instance of Audio Input Control Service from the pool.
|
||||
*
|
||||
* @return Audio Input Control Service instance in case of success or NULL in case of error.
|
||||
*/
|
||||
struct bt_aics *bt_aics_free_instance_get(void);
|
||||
|
||||
/**
|
||||
* @brief Get the service declaration attribute.
|
||||
*
|
||||
* The first service attribute returned can be included in any other GATT service.
|
||||
*
|
||||
* @param aics Audio Input Control Service instance.
|
||||
*
|
||||
* @return Pointer to the attributes of the service.
|
||||
*/
|
||||
void *bt_aics_svc_decl_get(struct bt_aics *aics);
|
||||
|
||||
/**
|
||||
* @brief Initialize the Audio Input Control Service instance.
|
||||
*
|
||||
* @param aics Audio Input Control Service instance.
|
||||
* @param init Audio Input Control Service initialization structure.
|
||||
*
|
||||
* @return 0 if success, ERRNO on failure.
|
||||
*/
|
||||
int bt_aics_register(struct bt_aics *aics, struct bt_aics_register_param *init);
|
||||
|
||||
/**
|
||||
* @brief Callback function for writes.
|
||||
*
|
||||
* @param conn Connection to peer device, or NULL if local server write.
|
||||
* @param inst The instance pointer.
|
||||
* @param err Error value. 0 on success, GATT error on positive value
|
||||
* or ERRNO on negative value.
|
||||
*/
|
||||
typedef void (*bt_aics_write_cb)(struct bt_conn *conn, struct bt_aics *inst,
|
||||
int err);
|
||||
|
||||
/**
|
||||
* @brief Callback function for the input state.
|
||||
*
|
||||
* 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 inst The instance pointer.
|
||||
* @param err Error value. 0 on success, GATT error on positive value
|
||||
* or ERRNO on negative value.
|
||||
* For notifications, this will always be 0.
|
||||
* @param gain The gain setting value.
|
||||
* @param mute The mute value.
|
||||
* @param mode The mode value.
|
||||
*/
|
||||
typedef void (*bt_aics_state_cb)(struct bt_conn *conn, struct bt_aics *inst,
|
||||
int err, int8_t gain, uint8_t mute,
|
||||
uint8_t mode);
|
||||
|
||||
/**
|
||||
* @brief Callback function for the gain settings.
|
||||
*
|
||||
* 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 inst The instance pointer.
|
||||
* @param err Error value. 0 on success, GATT error on positive value
|
||||
* or ERRNO on negative value.
|
||||
* For notifications, this will always be 0.
|
||||
* @param units The value that reflect the size of a single increment or decrement of the
|
||||
* Gain Setting value in 0.1 decibel units.
|
||||
* @param minimum The minimum gain allowed for the gain setting.
|
||||
* @param maximum The maximum gain allowed for the gain setting.
|
||||
*/
|
||||
typedef void (*bt_aics_gain_setting_cb)(struct bt_conn *conn,
|
||||
struct bt_aics *inst, int err,
|
||||
uint8_t units, int8_t minimum,
|
||||
int8_t maximum);
|
||||
|
||||
/**
|
||||
* @brief Callback function for the input type.
|
||||
*
|
||||
* 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 inst The instance pointer.
|
||||
* @param err Error value. 0 on success, GATT error on positive value
|
||||
* or ERRNO on negative value.
|
||||
* For notifications, this will always be 0.
|
||||
* @param type The input type.
|
||||
*/
|
||||
typedef void (*bt_aics_type_cb)(struct bt_conn *conn, struct bt_aics *inst,
|
||||
int err, uint8_t type);
|
||||
|
||||
/**
|
||||
* @brief Callback function for the input status.
|
||||
*
|
||||
* 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 inst The instance pointer.
|
||||
* @param err Error value. 0 on success, GATT error on positive value
|
||||
* or ERRNO on negative value.
|
||||
* For notifications, this will always be 0.
|
||||
* @param active Whether the instance is active or inactive.
|
||||
*/
|
||||
typedef void (*bt_aics_status_cb)(struct bt_conn *conn, struct bt_aics *inst,
|
||||
int err, bool active);
|
||||
|
||||
/**
|
||||
* @brief Callback function for the description.
|
||||
*
|
||||
* 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 inst The instance pointer.
|
||||
* @param err Error value. 0 on success, GATT error on positive value
|
||||
* or ERRNO on negative value.
|
||||
* For notifications, this will always be 0.
|
||||
* @param description The description as an UTF-8 encoded string (may have been clipped).
|
||||
*/
|
||||
typedef void (*bt_aics_description_cb)(struct bt_conn *conn,
|
||||
struct bt_aics *inst, int err,
|
||||
char *description);
|
||||
|
||||
/**
|
||||
* @brief Callback function for bt_aics_discover.
|
||||
*
|
||||
* This callback will usually be overwritten by the primary service that
|
||||
* includes the Audio Input Control Service client.
|
||||
*
|
||||
* @param conn Connection to peer device, or NULL if local server read.
|
||||
* @param inst The instance pointer.
|
||||
* @param err Error value. 0 on success, GATT error on positive value
|
||||
* or ERRNO on negative value.
|
||||
* For notifications, this will always be 0.
|
||||
*/
|
||||
typedef void (*bt_aics_discover_cb)(struct bt_conn *conn, struct bt_aics *inst,
|
||||
int err);
|
||||
|
||||
struct bt_aics_cb {
|
||||
bt_aics_state_cb state;
|
||||
bt_aics_gain_setting_cb gain_setting;
|
||||
bt_aics_type_cb type;
|
||||
bt_aics_status_cb status;
|
||||
bt_aics_description_cb description;
|
||||
|
||||
#if defined(CONFIG_BT_AICS_CLIENT)
|
||||
bt_aics_discover_cb discover;
|
||||
bt_aics_write_cb set_gain;
|
||||
bt_aics_write_cb unmute;
|
||||
bt_aics_write_cb mute;
|
||||
bt_aics_write_cb set_manual_mode;
|
||||
bt_aics_write_cb set_auto_mode;
|
||||
#endif /* CONFIG_BT_AICS_CLIENT */
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @brief Discover a Audio Input Control Service.
|
||||
*
|
||||
* Attempts to discover a Audio Input Control Service on a server given the
|
||||
* @p param.
|
||||
*
|
||||
* @param conn Connection to the peer with the Audio Input Control Service.
|
||||
* @param inst The instance pointer.
|
||||
* @param param Pointer to the parameters.
|
||||
*
|
||||
* @return 0 on success, ERRNO on fail.
|
||||
*/
|
||||
int bt_aics_discover(struct bt_conn *conn, struct bt_aics *inst,
|
||||
const struct bt_aics_discover_param *param);
|
||||
|
||||
/**
|
||||
* @brief Deactivates a 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 The instance pointer.
|
||||
*
|
||||
* @return 0 if success, ERRNO on failure.
|
||||
*/
|
||||
int bt_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_aics_deactivate.
|
||||
*
|
||||
* @param inst The instance pointer.
|
||||
*
|
||||
* @return 0 if success, ERRNO on failure.
|
||||
*/
|
||||
int bt_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 The instance pointer.
|
||||
*
|
||||
* @return 0 on success, GATT error value on fail.
|
||||
*/
|
||||
int bt_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 The instance pointer.
|
||||
*
|
||||
* @return 0 on success, GATT error value on fail.
|
||||
*/
|
||||
int bt_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 The instance pointer.
|
||||
*
|
||||
* @return 0 on success, GATT error value on fail.
|
||||
*/
|
||||
int bt_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 The instance pointer.
|
||||
*
|
||||
* @return 0 on success, GATT error value on fail.
|
||||
*/
|
||||
int bt_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 read local server value.
|
||||
* @param inst The instance pointer.
|
||||
*
|
||||
* @return 0 on success, GATT error value on fail.
|
||||
*/
|
||||
int bt_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 read local server value.
|
||||
* @param inst The instance pointer.
|
||||
*
|
||||
* @return 0 on success, GATT error value on fail.
|
||||
*/
|
||||
int bt_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 The instance pointer.
|
||||
*
|
||||
* @return 0 on success, GATT error value on fail.
|
||||
*/
|
||||
int bt_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 The instance pointer.
|
||||
*
|
||||
* @return 0 on success, GATT error value on fail.
|
||||
*/
|
||||
int bt_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 The instance pointer.
|
||||
* @param gain The gain to set (-128 to 127) in gain setting units
|
||||
* (see @ref bt_aics_gain_setting_cb).
|
||||
*
|
||||
* @return 0 on success, GATT error value on fail.
|
||||
*/
|
||||
int bt_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 The instance pointer.
|
||||
*
|
||||
* @return 0 on success, GATT error value on fail.
|
||||
*/
|
||||
int bt_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 The instance pointer.
|
||||
* @param description The description as an UTF-8 encoded string.
|
||||
*
|
||||
* @return 0 on success, GATT error value on fail.
|
||||
*/
|
||||
int bt_aics_description_set(struct bt_conn *conn, struct bt_aics *inst,
|
||||
const char *description);
|
||||
|
||||
/**
|
||||
* @brief Get a new Audio Input Control Service client instance.
|
||||
*
|
||||
* @return Pointer to the instance, or NULL if no free instances are left.
|
||||
*/
|
||||
struct bt_aics *bt_aics_client_free_instance_get(void);
|
||||
|
||||
/**
|
||||
* @brief Registers the callbacks for the Audio Input Control Service client.
|
||||
*
|
||||
* @param inst The instance pointer.
|
||||
* @param cb Pointer to the callback structure.
|
||||
*/
|
||||
void bt_aics_client_cb_register(struct bt_aics *inst, struct bt_aics_cb *cb);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @}
|
||||
*/
|
||||
|
||||
#endif /* ZEPHYR_INCLUDE_BLUETOOTH_SERVICES_AICS_H_ */
|
|
@ -410,6 +410,15 @@ struct bt_uuid_128 {
|
|||
*/
|
||||
#define BT_UUID_MESH_PROXY \
|
||||
BT_UUID_DECLARE_16(BT_UUID_MESH_PROXY_VAL)
|
||||
/** @def BT_UUID_AICS_VAL
|
||||
* @brief Audio Input Control Service value
|
||||
*/
|
||||
#define BT_UUID_AICS_VAL 0x1843
|
||||
/** @def BT_UUID_AICS
|
||||
* @brief Audio Input Control Service
|
||||
*/
|
||||
#define BT_UUID_AICS \
|
||||
BT_UUID_DECLARE_16(BT_UUID_AICS_VAL)
|
||||
/** @def BT_UUID_VOCS_VAL
|
||||
* @brief Volume Offset Control Service value
|
||||
*/
|
||||
|
@ -1313,6 +1322,60 @@ struct bt_uuid_128 {
|
|||
#define BT_UUID_GATT_SERVER_FEATURES \
|
||||
BT_UUID_DECLARE_16(BT_UUID_GATT_SERVER_FEATURES_VAL)
|
||||
|
||||
/** @def BT_UUID_AICS_STATE_VAL
|
||||
* @brief Audio Input Control Service State value
|
||||
*/
|
||||
#define BT_UUID_AICS_STATE_VAL 0x2B77
|
||||
/** @def BT_UUID_AICS_STATE
|
||||
* @brief Audio Input Control Service State
|
||||
*/
|
||||
#define BT_UUID_AICS_STATE \
|
||||
BT_UUID_DECLARE_16(BT_UUID_AICS_STATE_VAL)
|
||||
/** @def BT_UUID_AICS_GAIN_SETTINGS_VAL
|
||||
* @brief Audio Input Control Service Gain Settings Properties value
|
||||
*/
|
||||
#define BT_UUID_AICS_GAIN_SETTINGS_VAL 0x2B78
|
||||
/** @def BT_UUID_AICS_GAIN_SETTINGS
|
||||
* @brief Audio Input Control Service Gain Settings Properties
|
||||
*/
|
||||
#define BT_UUID_AICS_GAIN_SETTINGS \
|
||||
BT_UUID_DECLARE_16(BT_UUID_AICS_GAIN_SETTINGS_VAL)
|
||||
/** @def BT_UUID_AICS_INPUT_TYPE_VAL
|
||||
* @brief Audio Input Control Service Input Type value
|
||||
*/
|
||||
#define BT_UUID_AICS_INPUT_TYPE_VAL 0x2B79
|
||||
/** @def BT_UUID_AICS_INPUT_TYPE
|
||||
* @brief Audio Input Control Service Input Type
|
||||
*/
|
||||
#define BT_UUID_AICS_INPUT_TYPE \
|
||||
BT_UUID_DECLARE_16(BT_UUID_AICS_INPUT_TYPE_VAL)
|
||||
/** @def BT_UUID_AICS_INPUT_STATUS_VAL
|
||||
* @brief Audio Input Control Service Input Status value
|
||||
*/
|
||||
#define BT_UUID_AICS_INPUT_STATUS_VAL 0x2B7A
|
||||
/** @def BT_UUID_AICS_INPUT_STATUS
|
||||
* @brief Audio Input Control Service Input Status
|
||||
*/
|
||||
#define BT_UUID_AICS_INPUT_STATUS \
|
||||
BT_UUID_DECLARE_16(BT_UUID_AICS_INPUT_STATUS_VAL)
|
||||
/** @def BT_UUID_AICS_CONTROL_VAL
|
||||
* @brief Audio Input Control Service Control Point value
|
||||
*/
|
||||
#define BT_UUID_AICS_CONTROL_VAL 0x2B7B
|
||||
/** @def BT_UUID_AICS_CONTROL
|
||||
* @brief Audio Input Control Service Control Point
|
||||
*/
|
||||
#define BT_UUID_AICS_CONTROL \
|
||||
BT_UUID_DECLARE_16(BT_UUID_AICS_CONTROL_VAL)
|
||||
/** @def BT_UUID_AICS_DESCRIPTION_VAL
|
||||
* @brief Audio Input Control Service Input Description value
|
||||
*/
|
||||
#define BT_UUID_AICS_DESCRIPTION_VAL 0x2B7C
|
||||
/** @def BT_UUID_AICS_DESCRIPTION
|
||||
* @brief Audio Input Control Service Input Description
|
||||
*/
|
||||
#define BT_UUID_AICS_DESCRIPTION \
|
||||
BT_UUID_DECLARE_16(BT_UUID_AICS_DESCRIPTION_VAL)
|
||||
/** @def BT_UUID_VOCS_STATE_VAL
|
||||
* @brief Volume Offset State value
|
||||
*/
|
||||
|
@ -1349,7 +1412,6 @@ struct bt_uuid_128 {
|
|||
*/
|
||||
#define BT_UUID_VOCS_DESCRIPTION \
|
||||
BT_UUID_DECLARE_16(BT_UUID_VOCS_DESCRIPTION_VAL)
|
||||
|
||||
/*
|
||||
* Protocol UUIDs
|
||||
*/
|
||||
|
|
|
@ -4,3 +4,8 @@ if (CONFIG_BT_VOCS OR CONFIG_BT_VOCS_CLIENT)
|
|||
zephyr_library_sources(vocs.c)
|
||||
endif()
|
||||
zephyr_library_sources_ifdef(CONFIG_BT_VOCS_CLIENT vocs_client.c)
|
||||
|
||||
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)
|
||||
|
|
|
@ -50,7 +50,7 @@ config BT_AUDIO_DEBUG
|
|||
Use this option to enable debug logs for the Bluetooth
|
||||
Audio functionality.
|
||||
|
||||
|
||||
source "subsys/bluetooth/audio/Kconfig.vocs"
|
||||
source "subsys/bluetooth/audio/Kconfig.aics"
|
||||
|
||||
endif # BT_AUDIO
|
||||
|
|
76
subsys/bluetooth/audio/Kconfig.aics
Normal file
76
subsys/bluetooth/audio/Kconfig.aics
Normal file
|
@ -0,0 +1,76 @@
|
|||
# Bluetooth Audio - Audio Input Control Service options
|
||||
#
|
||||
# Copyright (c) 2020 Bose Corporation
|
||||
# Copyright (c) 2020 Nordic Semiconductor ASA
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
|
||||
if BT_AUDIO
|
||||
|
||||
##################### Audio Input Control Service #####################
|
||||
|
||||
config BT_AICS_MAX_INSTANCE_COUNT
|
||||
int "Audio Input Control Service max instance count"
|
||||
default 0
|
||||
range 0 15
|
||||
help
|
||||
This option sets the maximum number of instances of Audio Input
|
||||
Control Services.
|
||||
|
||||
config BT_AICS
|
||||
bool # hidden
|
||||
default y if BT_AICS_MAX_INSTANCE_COUNT > 0
|
||||
help
|
||||
This hidden option enables support for Audio Input Control Service.
|
||||
|
||||
if BT_AICS
|
||||
|
||||
config BT_AICS_MAX_INPUT_DESCRIPTION_SIZE
|
||||
int "Audio Input Control Service max input description size"
|
||||
default 32
|
||||
range 0 512
|
||||
help
|
||||
This option sets the maximum input description size in octets.
|
||||
############# DEBUG #############
|
||||
|
||||
config BT_DEBUG_AICS
|
||||
bool "Audio Input Control Service debug"
|
||||
depends on BT_AUDIO_DEBUG
|
||||
help
|
||||
Use this option to enable Audio Input Control Service debug logs for
|
||||
the Bluetooth Audio functionality.
|
||||
|
||||
endif # BT_AICS
|
||||
|
||||
##################### Audio Input Control Service Client #####################
|
||||
|
||||
config BT_AICS_CLIENT_MAX_INSTANCE_COUNT
|
||||
int "Audio Input Control Service client max instance count"
|
||||
default 0
|
||||
range 0 15
|
||||
help
|
||||
This option sets the maximum number of instances of Audio Input
|
||||
Control Services.
|
||||
|
||||
config BT_AICS_CLIENT
|
||||
bool # hidden
|
||||
default y if BT_AICS_CLIENT_MAX_INSTANCE_COUNT > 0
|
||||
help
|
||||
This hidden option enables support for Audio Input Control Service.
|
||||
|
||||
|
||||
if BT_AICS_CLIENT
|
||||
|
||||
############# DEBUG #############
|
||||
|
||||
config BT_DEBUG_AICS_CLIENT
|
||||
bool "Audio Input Control Service client debug"
|
||||
depends on BT_AUDIO_DEBUG
|
||||
help
|
||||
Use this option to enable Audio Input Control Service client debug
|
||||
logs for the Bluetooth Audio functionality.
|
||||
|
||||
endif # BT_AICS_CLIENT
|
||||
|
||||
endif # BT_AUDIO
|
814
subsys/bluetooth/audio/aics.c
Normal file
814
subsys/bluetooth/audio/aics.c
Normal file
|
@ -0,0 +1,814 @@
|
|||
/*
|
||||
* Copyright (c) 2020 Bose Corporation
|
||||
* Copyright (c) 2020 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/aics.h>
|
||||
|
||||
#include "aics_internal.h"
|
||||
|
||||
#define BT_DBG_ENABLED IS_ENABLED(CONFIG_BT_DEBUG_AICS)
|
||||
#define LOG_MODULE_NAME bt_aics
|
||||
#include "common/log.h"
|
||||
|
||||
#define VALID_AICS_OPCODE(opcode) \
|
||||
((opcode) >= BT_AICS_OPCODE_SET_GAIN && (opcode) <= BT_AICS_OPCODE_SET_AUTO)
|
||||
|
||||
#define AICS_CP_LEN 0x02
|
||||
#define AICS_CP_SET_GAIN_LEN 0x03
|
||||
|
||||
static void aics_state_cfg_changed(const struct bt_gatt_attr *attr,
|
||||
uint16_t value);
|
||||
static ssize_t read_aics_state(struct bt_conn *conn,
|
||||
const struct bt_gatt_attr *attr,
|
||||
void *buf, uint16_t len, uint16_t offset);
|
||||
static ssize_t read_aics_gain_settings(struct bt_conn *conn,
|
||||
const struct bt_gatt_attr *attr,
|
||||
void *buf, uint16_t len,
|
||||
uint16_t offset);
|
||||
static ssize_t read_type(struct bt_conn *conn, const struct bt_gatt_attr *attr,
|
||||
void *buf, uint16_t len, uint16_t offset);
|
||||
static void aics_input_status_cfg_changed(const struct bt_gatt_attr *attr,
|
||||
uint16_t value);
|
||||
static ssize_t read_input_status(struct bt_conn *conn,
|
||||
const struct bt_gatt_attr *attr,
|
||||
void *buf, uint16_t len, uint16_t offset);
|
||||
static ssize_t write_aics_control(struct bt_conn *conn,
|
||||
const struct bt_gatt_attr *attr,
|
||||
const void *buf, uint16_t len,
|
||||
uint16_t offset, uint8_t flags);
|
||||
static void aics_description_cfg_changed(const struct bt_gatt_attr *attr,
|
||||
uint16_t value);
|
||||
static ssize_t write_description(struct bt_conn *conn,
|
||||
const struct bt_gatt_attr *attr,
|
||||
const void *buf, uint16_t len, uint16_t offset,
|
||||
uint8_t flags);
|
||||
static ssize_t read_description(struct bt_conn *conn,
|
||||
const struct bt_gatt_attr *attr, void *buf,
|
||||
uint16_t len, uint16_t offset);
|
||||
|
||||
#if defined(CONFIG_BT_AICS)
|
||||
#define BT_AICS_SERVICE_DEFINITION(_aics) { \
|
||||
BT_GATT_SECONDARY_SERVICE(BT_UUID_AICS), \
|
||||
BT_GATT_CHARACTERISTIC(BT_UUID_AICS_STATE, \
|
||||
BT_GATT_CHRC_READ | BT_GATT_CHRC_NOTIFY, \
|
||||
BT_GATT_PERM_READ_ENCRYPT, \
|
||||
read_aics_state, NULL, &_aics), \
|
||||
BT_GATT_CCC(aics_state_cfg_changed, \
|
||||
BT_GATT_PERM_READ | BT_GATT_PERM_WRITE_ENCRYPT), \
|
||||
BT_GATT_CHARACTERISTIC(BT_UUID_AICS_GAIN_SETTINGS, \
|
||||
BT_GATT_CHRC_READ, \
|
||||
BT_GATT_PERM_READ_ENCRYPT, \
|
||||
read_aics_gain_settings, NULL, &_aics), \
|
||||
BT_GATT_CHARACTERISTIC(BT_UUID_AICS_INPUT_TYPE, \
|
||||
BT_GATT_CHRC_READ, \
|
||||
BT_GATT_PERM_READ_ENCRYPT, \
|
||||
read_type, NULL, &_aics), \
|
||||
BT_GATT_CHARACTERISTIC(BT_UUID_AICS_INPUT_STATUS, \
|
||||
BT_GATT_CHRC_READ | BT_GATT_CHRC_NOTIFY, \
|
||||
BT_GATT_PERM_READ_ENCRYPT, \
|
||||
read_input_status, NULL, &_aics), \
|
||||
BT_GATT_CCC(aics_input_status_cfg_changed, \
|
||||
BT_GATT_PERM_READ | BT_GATT_PERM_WRITE_ENCRYPT), \
|
||||
BT_GATT_CHARACTERISTIC(BT_UUID_AICS_CONTROL, \
|
||||
BT_GATT_CHRC_WRITE, \
|
||||
BT_GATT_PERM_WRITE_ENCRYPT, \
|
||||
NULL, write_aics_control, &_aics), \
|
||||
BT_GATT_CHARACTERISTIC(BT_UUID_AICS_DESCRIPTION, \
|
||||
BT_GATT_CHRC_READ | BT_GATT_CHRC_NOTIFY, \
|
||||
BT_GATT_PERM_READ_ENCRYPT, \
|
||||
read_description, NULL, &_aics), \
|
||||
BT_GATT_CCC(aics_description_cfg_changed, \
|
||||
BT_GATT_PERM_READ | BT_GATT_PERM_WRITE_ENCRYPT) \
|
||||
}
|
||||
|
||||
|
||||
static struct bt_aics_server aics_insts[CONFIG_BT_AICS_MAX_INSTANCE_COUNT];
|
||||
static uint32_t instance_cnt;
|
||||
BT_GATT_SERVICE_INSTANCE_DEFINE(aics_service_list, aics_insts,
|
||||
CONFIG_BT_AICS_MAX_INSTANCE_COUNT,
|
||||
BT_AICS_SERVICE_DEFINITION);
|
||||
|
||||
static void aics_state_cfg_changed(const struct bt_gatt_attr *attr,
|
||||
uint16_t value)
|
||||
{
|
||||
BT_DBG("value 0x%04x", value);
|
||||
}
|
||||
|
||||
static ssize_t read_aics_state(struct bt_conn *conn,
|
||||
const struct bt_gatt_attr *attr, void *buf,
|
||||
uint16_t len, uint16_t offset)
|
||||
{
|
||||
struct bt_aics_server *inst = attr->user_data;
|
||||
|
||||
BT_DBG("gain %d, mute %u, gain_mode %u, counter %u",
|
||||
inst->state.gain, inst->state.mute,
|
||||
inst->state.gain_mode, inst->state.change_counter);
|
||||
|
||||
return bt_gatt_attr_read(conn, attr, buf, len, offset, &inst->state,
|
||||
sizeof(inst->state));
|
||||
}
|
||||
|
||||
static ssize_t read_aics_gain_settings(struct bt_conn *conn,
|
||||
const struct bt_gatt_attr *attr,
|
||||
void *buf, uint16_t len, uint16_t offset)
|
||||
{
|
||||
struct bt_aics_server *inst = attr->user_data;
|
||||
|
||||
BT_DBG("units %u, min %d, max %d",
|
||||
inst->gain_settings.units, inst->gain_settings.minimum,
|
||||
inst->gain_settings.maximum);
|
||||
|
||||
return bt_gatt_attr_read(conn, attr, buf, len, offset,
|
||||
&inst->gain_settings,
|
||||
sizeof(inst->gain_settings));
|
||||
}
|
||||
|
||||
static ssize_t read_type(struct bt_conn *conn, const struct bt_gatt_attr *attr,
|
||||
void *buf, uint16_t len, uint16_t offset)
|
||||
{
|
||||
struct bt_aics_server *inst = attr->user_data;
|
||||
|
||||
BT_DBG("%u", inst->type);
|
||||
|
||||
return bt_gatt_attr_read(conn, attr, buf, len, offset, &inst->type,
|
||||
sizeof(inst->type));
|
||||
}
|
||||
|
||||
static void aics_input_status_cfg_changed(const struct bt_gatt_attr *attr,
|
||||
uint16_t value)
|
||||
{
|
||||
BT_DBG("value 0x%04x", value);
|
||||
}
|
||||
|
||||
static ssize_t read_input_status(struct bt_conn *conn,
|
||||
const struct bt_gatt_attr *attr, void *buf,
|
||||
uint16_t len, uint16_t offset)
|
||||
{
|
||||
struct bt_aics_server *inst = attr->user_data;
|
||||
|
||||
BT_DBG("%u", inst->status);
|
||||
|
||||
return bt_gatt_attr_read(conn, attr, buf, len, offset, &inst->status,
|
||||
sizeof(inst->status));
|
||||
}
|
||||
|
||||
#endif /* CONFIG_BT_AICS */
|
||||
|
||||
static ssize_t write_aics_control(struct bt_conn *conn,
|
||||
const struct bt_gatt_attr *attr,
|
||||
const void *buf, uint16_t len,
|
||||
uint16_t offset, uint8_t flags)
|
||||
{
|
||||
struct bt_aics_server *inst = attr->user_data;
|
||||
const struct bt_aics_gain_control *cp = buf;
|
||||
bool notify = false;
|
||||
|
||||
if (offset) {
|
||||
return BT_GATT_ERR(BT_ATT_ERR_INVALID_OFFSET);
|
||||
}
|
||||
|
||||
if (!len || !buf) {
|
||||
return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN);
|
||||
}
|
||||
|
||||
/* Check opcode before length */
|
||||
if (!VALID_AICS_OPCODE(cp->cp.opcode)) {
|
||||
BT_DBG("Invalid opcode %u", cp->cp.opcode);
|
||||
return BT_GATT_ERR(BT_AICS_ERR_OP_NOT_SUPPORTED);
|
||||
}
|
||||
|
||||
if ((len < AICS_CP_LEN) ||
|
||||
(len == AICS_CP_SET_GAIN_LEN && cp->cp.opcode != BT_AICS_OPCODE_SET_GAIN) ||
|
||||
(len > AICS_CP_SET_GAIN_LEN)) {
|
||||
return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN);
|
||||
}
|
||||
|
||||
BT_DBG("Opcode %u, counter %u", cp->cp.opcode, cp->cp.counter);
|
||||
if (cp->cp.counter != inst->state.change_counter) {
|
||||
return BT_GATT_ERR(BT_AICS_ERR_INVALID_COUNTER);
|
||||
}
|
||||
|
||||
switch (cp->cp.opcode) {
|
||||
case BT_AICS_OPCODE_SET_GAIN:
|
||||
BT_DBG("Set gain %d", cp->gain_setting);
|
||||
if (cp->gain_setting < inst->gain_settings.minimum ||
|
||||
cp->gain_setting > inst->gain_settings.maximum) {
|
||||
return BT_GATT_ERR(BT_AICS_ERR_OUT_OF_RANGE);
|
||||
}
|
||||
if (BT_AICS_INPUT_MODE_SETTABLE(inst->state.gain_mode) &&
|
||||
inst->state.gain != cp->gain_setting) {
|
||||
inst->state.gain = cp->gain_setting;
|
||||
notify = true;
|
||||
}
|
||||
break;
|
||||
case BT_AICS_OPCODE_UNMUTE:
|
||||
BT_DBG("Unmute");
|
||||
if (inst->state.mute == BT_AICS_STATE_MUTE_DISABLED) {
|
||||
return BT_GATT_ERR(BT_AICS_ERR_MUTE_DISABLED);
|
||||
}
|
||||
if (inst->state.mute != BT_AICS_STATE_UNMUTED) {
|
||||
inst->state.mute = BT_AICS_STATE_UNMUTED;
|
||||
notify = true;
|
||||
}
|
||||
break;
|
||||
case BT_AICS_OPCODE_MUTE:
|
||||
BT_DBG("Mute");
|
||||
if (inst->state.mute == BT_AICS_STATE_MUTE_DISABLED) {
|
||||
return BT_GATT_ERR(BT_AICS_ERR_MUTE_DISABLED);
|
||||
}
|
||||
if (inst->state.mute != BT_AICS_STATE_MUTED) {
|
||||
inst->state.mute = BT_AICS_STATE_MUTED;
|
||||
notify = true;
|
||||
}
|
||||
break;
|
||||
case BT_AICS_OPCODE_SET_MANUAL:
|
||||
BT_DBG("Set manual mode");
|
||||
if (BT_AICS_INPUT_MODE_IMMUTABLE(inst->state.gain_mode)) {
|
||||
return BT_GATT_ERR(BT_AICS_ERR_GAIN_MODE_NOT_ALLOWED);
|
||||
}
|
||||
if (inst->state.gain_mode != BT_AICS_MODE_MANUAL) {
|
||||
inst->state.gain_mode = BT_AICS_MODE_MANUAL;
|
||||
notify = true;
|
||||
}
|
||||
break;
|
||||
case BT_AICS_OPCODE_SET_AUTO:
|
||||
BT_DBG("Set automatic mode");
|
||||
if (BT_AICS_INPUT_MODE_IMMUTABLE(inst->state.gain_mode)) {
|
||||
return BT_GATT_ERR(BT_AICS_ERR_GAIN_MODE_NOT_ALLOWED);
|
||||
}
|
||||
if (inst->state.gain_mode != BT_AICS_MODE_AUTO) {
|
||||
inst->state.gain_mode = BT_AICS_MODE_AUTO;
|
||||
notify = true;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
return BT_GATT_ERR(BT_AICS_ERR_OP_NOT_SUPPORTED);
|
||||
}
|
||||
|
||||
if (notify) {
|
||||
inst->state.change_counter++;
|
||||
|
||||
BT_DBG("New state: gain %d, mute %u, gain_mode %u, counter %u",
|
||||
inst->state.gain, inst->state.mute,
|
||||
inst->state.gain_mode, inst->state.change_counter);
|
||||
|
||||
bt_gatt_notify_uuid(NULL, BT_UUID_AICS_STATE,
|
||||
inst->service_p->attrs, &inst->state,
|
||||
sizeof(inst->state));
|
||||
|
||||
if (inst->cb && inst->cb->state) {
|
||||
inst->cb->state(NULL, (struct bt_aics *)inst, 0,
|
||||
inst->state.gain, inst->state.mute,
|
||||
inst->state.gain_mode);
|
||||
} else {
|
||||
BT_DBG("Callback not registered for instance %p", inst);
|
||||
}
|
||||
}
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
#if defined(CONFIG_BT_AICS)
|
||||
static void aics_description_cfg_changed(const struct bt_gatt_attr *attr,
|
||||
uint16_t value)
|
||||
{
|
||||
BT_DBG("value 0x%04x", value);
|
||||
}
|
||||
#endif /* CONFIG_BT_AICS */
|
||||
|
||||
static ssize_t write_description(struct bt_conn *conn,
|
||||
const struct bt_gatt_attr *attr,
|
||||
const void *buf, uint16_t len, uint16_t offset,
|
||||
uint8_t flags)
|
||||
{
|
||||
struct bt_aics_server *inst = attr->user_data;
|
||||
|
||||
if (len >= sizeof(inst->description)) {
|
||||
BT_DBG("Output desc was clipped from length %u to %zu",
|
||||
len, sizeof(inst->description) - 1);
|
||||
/* We just clip the string value if it's too long */
|
||||
len = (uint16_t)sizeof(inst->description) - 1;
|
||||
}
|
||||
|
||||
if (memcmp(buf, inst->description, len)) {
|
||||
memcpy(inst->description, buf, len);
|
||||
inst->description[len] = '\0';
|
||||
|
||||
bt_gatt_notify_uuid(NULL, BT_UUID_AICS_DESCRIPTION,
|
||||
inst->service_p->attrs, &inst->description,
|
||||
strlen(inst->description));
|
||||
|
||||
if (inst->cb && inst->cb->description) {
|
||||
inst->cb->description(NULL, (struct bt_aics *)inst, 0,
|
||||
inst->description);
|
||||
} else {
|
||||
BT_DBG("Callback not registered for instance %p", inst);
|
||||
}
|
||||
}
|
||||
|
||||
BT_DBG("%s", log_strdup(inst->description));
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
#if defined(CONFIG_BT_AICS)
|
||||
static ssize_t read_description(struct bt_conn *conn,
|
||||
const struct bt_gatt_attr *attr, void *buf,
|
||||
uint16_t len, uint16_t offset)
|
||||
{
|
||||
struct bt_aics_server *inst = attr->user_data;
|
||||
|
||||
BT_DBG("%s", log_strdup(inst->description));
|
||||
|
||||
return bt_gatt_attr_read(conn, attr, buf, len, offset,
|
||||
&inst->description, strlen(inst->description));
|
||||
}
|
||||
|
||||
/************************ PUBLIC API ************************/
|
||||
void *bt_aics_svc_decl_get(struct bt_aics *aics)
|
||||
{
|
||||
CHECKIF(!aics) {
|
||||
BT_DBG("NULL instance");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return aics->srv.service_p->attrs;
|
||||
}
|
||||
|
||||
static void prepare_aics_instances(void)
|
||||
{
|
||||
for (int i = 0; i < ARRAY_SIZE(aics_insts); i++) {
|
||||
aics_insts[i].service_p = &aics_service_list[i];
|
||||
}
|
||||
}
|
||||
|
||||
int bt_aics_register(struct bt_aics *aics, struct bt_aics_register_param *param)
|
||||
{
|
||||
int err;
|
||||
static bool instance_prepared;
|
||||
|
||||
CHECKIF(!aics) {
|
||||
BT_DBG("NULL aics pointer");
|
||||
return -ENOTCONN;
|
||||
}
|
||||
|
||||
CHECKIF(!param) {
|
||||
BT_DBG("NULL param");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (!instance_prepared) {
|
||||
prepare_aics_instances();
|
||||
instance_prepared = true;
|
||||
}
|
||||
|
||||
CHECKIF(aics->srv.initialized) {
|
||||
return -EALREADY;
|
||||
}
|
||||
|
||||
CHECKIF(param->mute > BT_AICS_STATE_MUTE_DISABLED) {
|
||||
BT_DBG("Invalid AICS mute value: %u", param->mute);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
CHECKIF(param->gain_mode > BT_AICS_MODE_AUTO) {
|
||||
BT_DBG("Invalid AICS mode value: %u", param->gain_mode);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
CHECKIF(param->type > BT_AICS_INPUT_TYPE_STREAMING) {
|
||||
BT_DBG("Invalid AICS input type value: %u", param->type);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
CHECKIF(param->units == 0) {
|
||||
BT_DBG("AICS units value shall not be 0");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
CHECKIF(!(param->min_gain <= param->max_gain)) {
|
||||
BT_DBG("AICS min gain (%d) shall be lower than or equal to max gain (%d)",
|
||||
param->min_gain, param->max_gain);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
CHECKIF(param->gain < param->min_gain || param->gain > param->max_gain) {
|
||||
BT_DBG("AICS gain (%d) shall be not lower than min gain (%d) "
|
||||
"or higher than max gain (%d)",
|
||||
param->gain, param->min_gain, param->max_gain);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
aics->srv.state.gain = param->gain;
|
||||
aics->srv.state.mute = param->mute;
|
||||
aics->srv.state.gain_mode = param->gain_mode;
|
||||
aics->srv.gain_settings.units = param->units;
|
||||
aics->srv.gain_settings.minimum = param->min_gain;
|
||||
aics->srv.gain_settings.maximum = param->max_gain;
|
||||
aics->srv.type = param->type;
|
||||
aics->srv.status = param->status ? BT_AICS_STATUS_ACTIVE : BT_AICS_STATUS_INACTIVE;
|
||||
aics->srv.cb = param->cb;
|
||||
|
||||
if (param->description) {
|
||||
strncpy(aics->srv.description, param->description,
|
||||
sizeof(aics->srv.description) - 1);
|
||||
/* strncpy may not always null-terminate */
|
||||
aics->srv.description[sizeof(aics->srv.description) - 1] = '\0';
|
||||
if (IS_ENABLED(CONFIG_BT_DEBUG_AICS) &&
|
||||
strcmp(aics->srv.description, param->description)) {
|
||||
BT_DBG("Input desc clipped to %s",
|
||||
log_strdup(aics->srv.description));
|
||||
}
|
||||
}
|
||||
|
||||
/* Iterate over the attributes in AICS (starting from i = 1 to skip the
|
||||
* service declaration) to find the BT_UUID_AICS_DESCRIPTION and update
|
||||
* the characteristic value (at [i]), update that with the write
|
||||
* permission and callback, and also update the characteristic
|
||||
* declaration (always found at [i - 1]) with the
|
||||
* BT_GATT_CHRC_WRITE_WITHOUT_RESP property.
|
||||
*/
|
||||
if (param->desc_writable) {
|
||||
for (int i = 1; i < aics->srv.service_p->attr_count; i++) {
|
||||
struct bt_gatt_attr *attr;
|
||||
|
||||
attr = &aics->srv.service_p->attrs[i];
|
||||
|
||||
if (!bt_uuid_cmp(attr->uuid, BT_UUID_AICS_DESCRIPTION)) {
|
||||
/* Update attr and chrc to be writable */
|
||||
struct bt_gatt_chrc *chrc;
|
||||
|
||||
chrc = aics->srv.service_p->attrs[i - 1].user_data;
|
||||
attr->write = write_description;
|
||||
attr->perm |= BT_GATT_PERM_WRITE_ENCRYPT;
|
||||
chrc->properties |= BT_GATT_CHRC_WRITE_WITHOUT_RESP;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
err = bt_gatt_service_register(aics->srv.service_p);
|
||||
if (err) {
|
||||
BT_DBG("Could not register AICS service");
|
||||
return err;
|
||||
}
|
||||
|
||||
aics->srv.initialized = true;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct bt_aics *bt_aics_free_instance_get(void)
|
||||
{
|
||||
if (instance_cnt >= CONFIG_BT_AICS_MAX_INSTANCE_COUNT) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return (struct bt_aics *)&aics_insts[instance_cnt++];
|
||||
}
|
||||
|
||||
/****************************** PUBLIC API ******************************/
|
||||
int bt_aics_deactivate(struct bt_aics *inst)
|
||||
{
|
||||
CHECKIF(!inst) {
|
||||
BT_DBG("NULL instance");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (inst->srv.status == BT_AICS_STATUS_ACTIVE) {
|
||||
inst->srv.status = BT_AICS_STATUS_INACTIVE;
|
||||
BT_DBG("Instance %p: Status was set to inactive", inst);
|
||||
|
||||
bt_gatt_notify_uuid(NULL, BT_UUID_AICS_INPUT_STATUS,
|
||||
inst->srv.service_p->attrs,
|
||||
&inst->srv.status,
|
||||
sizeof(inst->srv.status));
|
||||
|
||||
if (inst->srv.cb && inst->srv.cb->status) {
|
||||
inst->srv.cb->status(NULL, inst, 0, inst->srv.status);
|
||||
} else {
|
||||
BT_DBG("Callback not registered for instance %p", inst);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int bt_aics_activate(struct bt_aics *inst)
|
||||
{
|
||||
CHECKIF(!inst) {
|
||||
BT_DBG("NULL instance");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (inst->srv.status == BT_AICS_STATUS_INACTIVE) {
|
||||
inst->srv.status = BT_AICS_STATUS_ACTIVE;
|
||||
BT_DBG("Instance %p: Status was set to active", inst);
|
||||
|
||||
bt_gatt_notify_uuid(NULL, BT_UUID_AICS_INPUT_STATUS,
|
||||
inst->srv.service_p->attrs,
|
||||
&inst->srv.status,
|
||||
sizeof(inst->srv.status));
|
||||
|
||||
if (inst->srv.cb && inst->srv.cb->status) {
|
||||
inst->srv.cb->status(NULL, inst, 0, inst->srv.status);
|
||||
} else {
|
||||
BT_DBG("Callback not registered for instance %p", inst);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif /* CONFIG_BT_AICS */
|
||||
|
||||
int bt_aics_state_get(struct bt_conn *conn, struct bt_aics *inst)
|
||||
{
|
||||
CHECKIF(!inst) {
|
||||
BT_DBG("NULL instance");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (IS_ENABLED(CONFIG_BT_AICS_CLIENT) && conn) {
|
||||
return bt_aics_client_state_get(conn, inst);
|
||||
} else if (IS_ENABLED(CONFIG_BT_AICS) && !conn) {
|
||||
if (inst->srv.cb && inst->srv.cb->state) {
|
||||
inst->srv.cb->state(NULL, inst, 0, inst->srv.state.gain,
|
||||
inst->srv.state.mute,
|
||||
inst->srv.state.gain_mode);
|
||||
} else {
|
||||
BT_DBG("Callback not registered for instance %p", inst);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
return -ENOTSUP;
|
||||
}
|
||||
|
||||
int bt_aics_gain_setting_get(struct bt_conn *conn, struct bt_aics *inst)
|
||||
{
|
||||
CHECKIF(!inst) {
|
||||
BT_DBG("NULL instance");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (IS_ENABLED(CONFIG_BT_AICS_CLIENT) && conn) {
|
||||
return bt_aics_client_gain_setting_get(conn, inst);
|
||||
} else if (IS_ENABLED(CONFIG_BT_AICS) && !conn) {
|
||||
if (inst->srv.cb && inst->srv.cb->gain_setting) {
|
||||
inst->srv.cb->gain_setting(NULL, inst, 0,
|
||||
inst->srv.gain_settings.units,
|
||||
inst->srv.gain_settings.minimum,
|
||||
inst->srv.gain_settings.maximum);
|
||||
} else {
|
||||
BT_DBG("Callback not registered for instance %p", inst);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
return -ENOTSUP;
|
||||
}
|
||||
|
||||
int bt_aics_type_get(struct bt_conn *conn, struct bt_aics *inst)
|
||||
{
|
||||
CHECKIF(!inst) {
|
||||
BT_DBG("NULL instance");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (IS_ENABLED(CONFIG_BT_AICS_CLIENT) && conn) {
|
||||
return bt_aics_client_type_get(conn, inst);
|
||||
} else if (IS_ENABLED(CONFIG_BT_AICS) && !conn) {
|
||||
if (inst->srv.cb && inst->srv.cb->type) {
|
||||
inst->srv.cb->type(NULL, inst, 0, inst->srv.type);
|
||||
} else {
|
||||
BT_DBG("Callback not registered for instance %p", inst);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
return -ENOTSUP;
|
||||
}
|
||||
|
||||
int bt_aics_status_get(struct bt_conn *conn, struct bt_aics *inst)
|
||||
{
|
||||
CHECKIF(!inst) {
|
||||
BT_DBG("NULL instance");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (IS_ENABLED(CONFIG_BT_AICS_CLIENT) && conn) {
|
||||
return bt_aics_client_status_get(conn, inst);
|
||||
} else if (IS_ENABLED(CONFIG_BT_AICS) && !conn) {
|
||||
if (inst->srv.cb && inst->srv.cb->status) {
|
||||
inst->srv.cb->status(NULL, inst, 0, inst->srv.status);
|
||||
} else {
|
||||
BT_DBG("Callback not registered for instance %p", inst);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
return -ENOTSUP;
|
||||
}
|
||||
|
||||
int bt_aics_unmute(struct bt_conn *conn, struct bt_aics *inst)
|
||||
{
|
||||
CHECKIF(!inst) {
|
||||
BT_DBG("NULL instance");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (IS_ENABLED(CONFIG_BT_AICS_CLIENT) && conn) {
|
||||
return bt_aics_client_unmute(conn, inst);
|
||||
} else if (IS_ENABLED(CONFIG_BT_AICS) && !conn) {
|
||||
struct bt_gatt_attr attr;
|
||||
struct bt_aics_control cp;
|
||||
int err;
|
||||
|
||||
cp.opcode = BT_AICS_OPCODE_UNMUTE;
|
||||
cp.counter = inst->srv.state.change_counter;
|
||||
|
||||
attr.user_data = inst;
|
||||
|
||||
err = write_aics_control(NULL, &attr, &cp, sizeof(cp), 0, 0);
|
||||
|
||||
return err > 0 ? 0 : err;
|
||||
}
|
||||
|
||||
return -ENOTSUP;
|
||||
}
|
||||
|
||||
int bt_aics_mute(struct bt_conn *conn, struct bt_aics *inst)
|
||||
{
|
||||
CHECKIF(!inst) {
|
||||
BT_DBG("NULL instance");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (IS_ENABLED(CONFIG_BT_AICS_CLIENT) && conn) {
|
||||
return bt_aics_client_mute(conn, inst);
|
||||
} else if (IS_ENABLED(CONFIG_BT_AICS) && !conn) {
|
||||
struct bt_gatt_attr attr;
|
||||
struct bt_aics_control cp;
|
||||
int err;
|
||||
|
||||
cp.opcode = BT_AICS_OPCODE_MUTE;
|
||||
cp.counter = inst->srv.state.change_counter;
|
||||
|
||||
attr.user_data = inst;
|
||||
|
||||
err = write_aics_control(NULL, &attr, &cp, sizeof(cp), 0, 0);
|
||||
|
||||
return err > 0 ? 0 : err;
|
||||
}
|
||||
|
||||
return -ENOTSUP;
|
||||
}
|
||||
|
||||
int bt_aics_manual_gain_set(struct bt_conn *conn, struct bt_aics *inst)
|
||||
{
|
||||
CHECKIF(!inst) {
|
||||
BT_DBG("NULL instance");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (IS_ENABLED(CONFIG_BT_AICS_CLIENT) && conn) {
|
||||
return bt_aics_client_manual_gain_set(conn, inst);
|
||||
} else if (IS_ENABLED(CONFIG_BT_AICS) && !conn) {
|
||||
struct bt_gatt_attr attr;
|
||||
struct bt_aics_control cp;
|
||||
int err;
|
||||
|
||||
cp.opcode = BT_AICS_OPCODE_SET_MANUAL;
|
||||
cp.counter = inst->srv.state.change_counter;
|
||||
|
||||
attr.user_data = inst;
|
||||
|
||||
err = write_aics_control(NULL, &attr, &cp, sizeof(cp), 0, 0);
|
||||
|
||||
return err > 0 ? 0 : err;
|
||||
}
|
||||
|
||||
return -ENOTSUP;
|
||||
}
|
||||
|
||||
int bt_aics_automatic_gain_set(struct bt_conn *conn, struct bt_aics *inst)
|
||||
{
|
||||
CHECKIF(!inst) {
|
||||
BT_DBG("NULL instance");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (IS_ENABLED(CONFIG_BT_AICS_CLIENT) && conn) {
|
||||
return bt_aics_client_automatic_gain_set(conn, inst);
|
||||
} else if (IS_ENABLED(CONFIG_BT_AICS) && !conn) {
|
||||
struct bt_gatt_attr attr;
|
||||
struct bt_aics_control cp;
|
||||
int err;
|
||||
|
||||
cp.opcode = BT_AICS_OPCODE_SET_AUTO;
|
||||
cp.counter = inst->srv.state.change_counter;
|
||||
|
||||
attr.user_data = inst;
|
||||
|
||||
err = write_aics_control(NULL, &attr, &cp, sizeof(cp), 0, 0);
|
||||
|
||||
return err > 0 ? 0 : err;
|
||||
}
|
||||
|
||||
return -ENOTSUP;
|
||||
}
|
||||
|
||||
int bt_aics_gain_set(struct bt_conn *conn, struct bt_aics *inst, int8_t gain)
|
||||
{
|
||||
CHECKIF(!inst) {
|
||||
BT_DBG("NULL instance");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (IS_ENABLED(CONFIG_BT_AICS_CLIENT) && conn) {
|
||||
return bt_aics_client_gain_set(conn, inst, gain);
|
||||
} else if (IS_ENABLED(CONFIG_BT_AICS) && !conn) {
|
||||
struct bt_gatt_attr attr;
|
||||
struct bt_aics_gain_control cp;
|
||||
int err;
|
||||
|
||||
cp.cp.opcode = BT_AICS_OPCODE_SET_GAIN;
|
||||
cp.cp.counter = inst->srv.state.change_counter;
|
||||
cp.gain_setting = gain;
|
||||
|
||||
attr.user_data = inst;
|
||||
|
||||
err = write_aics_control(NULL, &attr, &cp, sizeof(cp), 0, 0);
|
||||
|
||||
return err > 0 ? 0 : err;
|
||||
}
|
||||
|
||||
return -ENOTSUP;
|
||||
}
|
||||
|
||||
int bt_aics_description_get(struct bt_conn *conn, struct bt_aics *inst)
|
||||
{
|
||||
CHECKIF(!inst) {
|
||||
BT_DBG("NULL instance");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (IS_ENABLED(CONFIG_BT_AICS_CLIENT) && conn) {
|
||||
return bt_aics_client_description_get(conn, inst);
|
||||
} else if (IS_ENABLED(CONFIG_BT_AICS) && !conn) {
|
||||
if (inst->srv.cb && inst->srv.cb->description) {
|
||||
inst->srv.cb->description(NULL, inst, 0,
|
||||
inst->srv.description);
|
||||
} else {
|
||||
BT_DBG("Callback not registered for instance %p", inst);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
return -ENOTSUP;
|
||||
}
|
||||
|
||||
int bt_aics_description_set(struct bt_conn *conn, struct bt_aics *inst,
|
||||
const char *description)
|
||||
{
|
||||
CHECKIF(!inst) {
|
||||
BT_DBG("NULL instance");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
CHECKIF(!description) {
|
||||
BT_DBG("NULL description");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (IS_ENABLED(CONFIG_BT_AICS_CLIENT) && conn) {
|
||||
return bt_aics_client_description_set(conn, inst, description);
|
||||
} else if (IS_ENABLED(CONFIG_BT_AICS) && !conn) {
|
||||
struct bt_gatt_attr attr;
|
||||
int err;
|
||||
|
||||
attr.user_data = inst;
|
||||
|
||||
err = write_description(NULL, &attr, description,
|
||||
strlen(description), 0, 0);
|
||||
|
||||
return err > 0 ? 0 : err;
|
||||
}
|
||||
|
||||
return -ENOTSUP;
|
||||
}
|
939
subsys/bluetooth/audio/aics_client.c
Normal file
939
subsys/bluetooth/audio/aics_client.c
Normal file
|
@ -0,0 +1,939 @@
|
|||
/* Bluetooth AICS client */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2020 Bose Corporation
|
||||
* Copyright (c) 2020 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/aics.h>
|
||||
|
||||
#include "aics_internal.h"
|
||||
|
||||
#define BT_DBG_ENABLED IS_ENABLED(CONFIG_BT_DEBUG_AICS_CLIENT)
|
||||
#define LOG_MODULE_NAME bt_aics_client
|
||||
#include "common/log.h"
|
||||
|
||||
static struct bt_aics aics_insts[CONFIG_BT_MAX_CONN * CONFIG_BT_AICS_CLIENT_MAX_INSTANCE_COUNT];
|
||||
|
||||
static int aics_client_common_control(struct bt_conn *conn, uint8_t opcode, struct bt_aics *inst);
|
||||
|
||||
static struct bt_aics *lookup_aics_by_handle(struct bt_conn *conn, uint16_t handle)
|
||||
{
|
||||
for (int i = 0; i < ARRAY_SIZE(aics_insts); i++) {
|
||||
if (aics_insts[i].cli.conn == conn &&
|
||||
aics_insts[i].cli.active &&
|
||||
aics_insts[i].cli.start_handle <= handle &&
|
||||
aics_insts[i].cli.end_handle >= handle) {
|
||||
return &aics_insts[i];
|
||||
}
|
||||
}
|
||||
|
||||
BT_DBG("Could not find AICS instance with handle 0x%04x", handle);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
uint8_t aics_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 bt_aics *inst = lookup_aics_by_handle(conn, handle);
|
||||
struct bt_aics_state *state;
|
||||
uint8_t *status;
|
||||
|
||||
if (!inst) {
|
||||
BT_DBG("Instance not found");
|
||||
return BT_GATT_ITER_STOP;
|
||||
}
|
||||
|
||||
if (!data || !length) {
|
||||
return BT_GATT_ITER_CONTINUE;
|
||||
}
|
||||
|
||||
if (handle == inst->cli.state_handle) {
|
||||
if (length == sizeof(*state)) {
|
||||
state = (struct bt_aics_state *)data;
|
||||
BT_DBG("Inst %p: Gain %d, mute %u, gain_mode %u, counter %u",
|
||||
inst, state->gain, state->mute, state->gain_mode,
|
||||
state->change_counter);
|
||||
|
||||
inst->cli.change_counter = state->change_counter;
|
||||
|
||||
if (inst->cli.cb && inst->cli.cb->state) {
|
||||
inst->cli.cb->state(conn, inst, 0, state->gain,
|
||||
state->mute, state->gain_mode);
|
||||
}
|
||||
}
|
||||
} else if (handle == inst->cli.status_handle) {
|
||||
if (length == sizeof(*status)) {
|
||||
status = (uint8_t *)data;
|
||||
BT_DBG("Inst %p: Status %u", inst, *status);
|
||||
if (inst->cli.cb && inst->cli.cb->status) {
|
||||
inst->cli.cb->status(conn, inst, 0, *status);
|
||||
}
|
||||
}
|
||||
} else if (handle == inst->cli.desc_handle) {
|
||||
char desc[MIN(CONFIG_BT_L2CAP_RX_MTU, BT_ATT_MAX_ATTRIBUTE_LEN) + 1];
|
||||
|
||||
/* Truncate if too large */
|
||||
if (length > sizeof(desc) - 1) {
|
||||
BT_DBG("Description truncated from %u to %zu octets",
|
||||
length, sizeof(desc) - 1);
|
||||
}
|
||||
length = MIN(sizeof(desc) - 1, length);
|
||||
|
||||
memcpy(desc, data, length);
|
||||
desc[length] = '\0';
|
||||
BT_DBG("Inst %p: Input description: %s", inst, log_strdup(desc));
|
||||
if (inst->cli.cb && inst->cli.cb->description) {
|
||||
inst->cli.cb->description(conn, inst, 0, desc);
|
||||
}
|
||||
}
|
||||
|
||||
return BT_GATT_ITER_CONTINUE;
|
||||
}
|
||||
|
||||
static uint8_t aics_client_read_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 bt_aics *inst = lookup_aics_by_handle(conn, params->single.handle);
|
||||
struct bt_aics_state *state = (struct bt_aics_state *)data;
|
||||
|
||||
memset(params, 0, sizeof(*params));
|
||||
|
||||
if (!inst) {
|
||||
BT_DBG("Instance not found");
|
||||
return BT_GATT_ITER_STOP;
|
||||
}
|
||||
|
||||
BT_DBG("Inst %p: err: 0x%02X", inst, err);
|
||||
inst->cli.busy = false;
|
||||
|
||||
if (cb_err) {
|
||||
BT_DBG("State read failed: %d", err);
|
||||
if (inst->cli.cb && inst->cli.cb->state) {
|
||||
inst->cli.cb->state(conn, inst, cb_err, 0, 0, 0);
|
||||
}
|
||||
return BT_GATT_ITER_STOP;
|
||||
}
|
||||
|
||||
if (data) {
|
||||
if (length == sizeof(*state)) {
|
||||
BT_DBG("Gain %d, mute %u, gain_mode %u, counter %u",
|
||||
state->gain, state->mute, state->gain_mode,
|
||||
state->change_counter);
|
||||
|
||||
inst->cli.change_counter = state->change_counter;
|
||||
} else {
|
||||
BT_DBG("Invalid length %u (expected %zu)",
|
||||
length, sizeof(*state));
|
||||
cb_err = BT_ATT_ERR_INVALID_ATTRIBUTE_LEN;
|
||||
}
|
||||
} else {
|
||||
BT_DBG("Invalid state");
|
||||
cb_err = BT_ATT_ERR_UNLIKELY;
|
||||
}
|
||||
|
||||
if (inst->cli.cb && inst->cli.cb->state) {
|
||||
inst->cli.cb->state(conn, inst, cb_err, state->gain,
|
||||
state->mute, state->gain_mode);
|
||||
}
|
||||
|
||||
return BT_GATT_ITER_STOP;
|
||||
}
|
||||
|
||||
static uint8_t aics_client_read_gain_settings_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 bt_aics *inst = lookup_aics_by_handle(conn, params->single.handle);
|
||||
struct bt_aics_gain_settings *gain_settings = (struct bt_aics_gain_settings *)data;
|
||||
|
||||
memset(params, 0, sizeof(*params));
|
||||
|
||||
if (!inst) {
|
||||
BT_DBG("Instance not found");
|
||||
return BT_GATT_ITER_STOP;
|
||||
}
|
||||
|
||||
BT_DBG("Inst %p: err: 0x%02X", inst, err);
|
||||
inst->cli.busy = false;
|
||||
|
||||
if (cb_err) {
|
||||
BT_DBG("Gain settings read failed: %d", err);
|
||||
if (inst->cli.cb && inst->cli.cb->gain_setting) {
|
||||
inst->cli.cb->gain_setting(conn, inst, cb_err, 0, 0, 0);
|
||||
}
|
||||
return BT_GATT_ITER_STOP;
|
||||
}
|
||||
|
||||
if (data) {
|
||||
if (length == sizeof(*gain_settings)) {
|
||||
BT_DBG("Units %u, Max %d, Min %d", gain_settings->units,
|
||||
gain_settings->maximum, gain_settings->minimum);
|
||||
} else {
|
||||
BT_DBG("Invalid length %u (expected %zu)", length, sizeof(*gain_settings));
|
||||
cb_err = BT_ATT_ERR_INVALID_ATTRIBUTE_LEN;
|
||||
}
|
||||
} else {
|
||||
BT_DBG("Invalid gain settings");
|
||||
cb_err = BT_ATT_ERR_UNLIKELY;
|
||||
}
|
||||
|
||||
if (inst->cli.cb && inst->cli.cb->gain_setting) {
|
||||
inst->cli.cb->gain_setting(conn, inst, cb_err, gain_settings->units,
|
||||
gain_settings->minimum, gain_settings->maximum);
|
||||
}
|
||||
|
||||
return BT_GATT_ITER_STOP;
|
||||
}
|
||||
|
||||
static uint8_t aics_client_read_type_cb(struct bt_conn *conn, uint8_t err,
|
||||
struct bt_gatt_read_params *params,
|
||||
const void *data, uint16_t length)
|
||||
{
|
||||
int cb_err = err;
|
||||
uint8_t *type = (uint8_t *)data;
|
||||
struct bt_aics *inst = lookup_aics_by_handle(conn, params->single.handle);
|
||||
|
||||
memset(params, 0, sizeof(*params));
|
||||
|
||||
if (!inst) {
|
||||
BT_DBG("Instance not found");
|
||||
return BT_GATT_ITER_STOP;
|
||||
}
|
||||
|
||||
BT_DBG("Inst %p: err: 0x%02X", inst, err);
|
||||
inst->cli.busy = false;
|
||||
|
||||
if (cb_err) {
|
||||
BT_DBG("Type read failed: %d", err);
|
||||
if (inst->cli.cb && inst->cli.cb->type) {
|
||||
inst->cli.cb->type(conn, inst, cb_err, 0);
|
||||
}
|
||||
return BT_GATT_ITER_STOP;
|
||||
}
|
||||
|
||||
if (data) {
|
||||
if (length == sizeof(*type)) {
|
||||
BT_DBG("Type %u", *type);
|
||||
} else {
|
||||
BT_DBG("Invalid length %u (expected %zu)", length, sizeof(*type));
|
||||
cb_err = BT_ATT_ERR_INVALID_ATTRIBUTE_LEN;
|
||||
}
|
||||
} else {
|
||||
BT_DBG("Invalid type");
|
||||
cb_err = BT_ATT_ERR_UNLIKELY;
|
||||
}
|
||||
|
||||
if (inst->cli.cb && inst->cli.cb->type) {
|
||||
inst->cli.cb->type(conn, inst, cb_err, *type);
|
||||
}
|
||||
|
||||
return BT_GATT_ITER_STOP;
|
||||
}
|
||||
|
||||
static uint8_t aics_client_read_status_cb(struct bt_conn *conn, uint8_t err,
|
||||
struct bt_gatt_read_params *params,
|
||||
const void *data, uint16_t length)
|
||||
{
|
||||
int cb_err = err;
|
||||
uint8_t *status = (uint8_t *)data;
|
||||
struct bt_aics *inst = lookup_aics_by_handle(conn, params->single.handle);
|
||||
|
||||
memset(params, 0, sizeof(*params));
|
||||
|
||||
if (!inst) {
|
||||
BT_DBG("Instance not found");
|
||||
return BT_GATT_ITER_STOP;
|
||||
}
|
||||
|
||||
BT_DBG("Inst %p: err: 0x%02X", inst, err);
|
||||
inst->cli.busy = false;
|
||||
|
||||
if (cb_err) {
|
||||
BT_DBG("Status read failed: %d", err);
|
||||
if (inst->cli.cb && inst->cli.cb->status) {
|
||||
inst->cli.cb->status(conn, inst, cb_err, 0);
|
||||
}
|
||||
return BT_GATT_ITER_STOP;
|
||||
}
|
||||
|
||||
if (data) {
|
||||
if (length == sizeof(*status)) {
|
||||
BT_DBG("Status %u", *status);
|
||||
} else {
|
||||
BT_DBG("Invalid length %u (expected %zu)", length, sizeof(*status));
|
||||
cb_err = BT_ATT_ERR_INVALID_ATTRIBUTE_LEN;
|
||||
}
|
||||
} else {
|
||||
BT_DBG("Invalid status");
|
||||
cb_err = BT_ATT_ERR_UNLIKELY;
|
||||
}
|
||||
|
||||
if (inst->cli.cb && inst->cli.cb->status) {
|
||||
inst->cli.cb->status(conn, inst, cb_err, *status);
|
||||
}
|
||||
|
||||
return BT_GATT_ITER_STOP;
|
||||
}
|
||||
|
||||
static void aics_cp_notify_app(struct bt_conn *conn, struct bt_aics *inst, uint8_t err)
|
||||
{
|
||||
if (!inst->cli.cb) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (inst->cli.cp_val.cp.opcode) {
|
||||
case BT_AICS_OPCODE_SET_GAIN:
|
||||
if (inst->cli.cb->set_gain) {
|
||||
inst->cli.cb->set_gain(conn, inst, err);
|
||||
}
|
||||
break;
|
||||
case BT_AICS_OPCODE_UNMUTE:
|
||||
if (inst->cli.cb->unmute) {
|
||||
inst->cli.cb->unmute(conn, inst, err);
|
||||
}
|
||||
break;
|
||||
case BT_AICS_OPCODE_MUTE:
|
||||
if (inst->cli.cb->mute) {
|
||||
inst->cli.cb->mute(conn, inst, err);
|
||||
}
|
||||
break;
|
||||
case BT_AICS_OPCODE_SET_MANUAL:
|
||||
if (inst->cli.cb->set_manual_mode) {
|
||||
inst->cli.cb->set_manual_mode(conn, inst, err);
|
||||
}
|
||||
break;
|
||||
case BT_AICS_OPCODE_SET_AUTO:
|
||||
if (inst->cli.cb->set_auto_mode) {
|
||||
inst->cli.cb->set_auto_mode(conn, inst, err);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
BT_DBG("Unknown opcode 0x%02x", inst->cli.cp_val.cp.opcode);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static uint8_t internal_read_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 bt_aics *inst = lookup_aics_by_handle(conn, params->single.handle);
|
||||
struct bt_aics_state *state = (struct bt_aics_state *)data;
|
||||
|
||||
memset(params, 0, sizeof(*params));
|
||||
|
||||
if (!inst) {
|
||||
BT_ERR("Instance not found");
|
||||
return BT_GATT_ITER_STOP;
|
||||
}
|
||||
|
||||
if (err) {
|
||||
BT_WARN("State read failed: %d", err);
|
||||
} else if (data) {
|
||||
if (length == sizeof(*state)) {
|
||||
int write_err;
|
||||
|
||||
BT_DBG("Gain %d, mute %u, gain_mode %u, counter %u",
|
||||
state->gain, state->mute,
|
||||
state->gain_mode, state->change_counter);
|
||||
inst->cli.change_counter = state->change_counter;
|
||||
|
||||
/* clear busy flag to reuse function */
|
||||
inst->cli.busy = false;
|
||||
|
||||
if (inst->cli.cp_val.cp.opcode == BT_AICS_OPCODE_SET_GAIN) {
|
||||
write_err = bt_aics_client_gain_set(conn, inst,
|
||||
inst->cli.cp_val.gain_setting);
|
||||
} else {
|
||||
write_err = aics_client_common_control(conn,
|
||||
inst->cli.cp_val.cp.opcode,
|
||||
inst);
|
||||
}
|
||||
|
||||
if (write_err) {
|
||||
cb_err = BT_ATT_ERR_UNLIKELY;
|
||||
}
|
||||
} else {
|
||||
BT_DBG("Invalid length %u (expected %zu)", length, sizeof(*state));
|
||||
cb_err = BT_ATT_ERR_UNLIKELY;
|
||||
}
|
||||
}
|
||||
|
||||
if (cb_err) {
|
||||
inst->cli.busy = false;
|
||||
aics_cp_notify_app(conn, inst, cb_err);
|
||||
}
|
||||
|
||||
return BT_GATT_ITER_STOP;
|
||||
}
|
||||
|
||||
|
||||
static void aics_client_write_aics_cp_cb(struct bt_conn *conn, uint8_t err,
|
||||
struct bt_gatt_write_params *params)
|
||||
{
|
||||
int cb_err = err;
|
||||
struct bt_aics *inst = lookup_aics_by_handle(conn, params->handle);
|
||||
|
||||
memset(params, 0, sizeof(*params));
|
||||
|
||||
if (!inst) {
|
||||
BT_DBG("Instance not found");
|
||||
return;
|
||||
}
|
||||
|
||||
BT_DBG("Inst %p: err: %d", inst, cb_err);
|
||||
|
||||
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) {
|
||||
inst->cli.read_params.func = internal_read_state_cb;
|
||||
inst->cli.read_params.handle_count = 1;
|
||||
inst->cli.read_params.single.handle = inst->cli.state_handle;
|
||||
inst->cli.read_params.single.offset = 0U;
|
||||
|
||||
cb_err = bt_gatt_read(conn, &inst->cli.read_params);
|
||||
|
||||
if (cb_err) {
|
||||
BT_WARN("Could not read state: %d", cb_err);
|
||||
} else {
|
||||
inst->cli.cp_retried = true;
|
||||
/* Wait for read callback */
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
inst->cli.busy = false;
|
||||
inst->cli.cp_retried = false;
|
||||
|
||||
aics_cp_notify_app(conn, inst, cb_err);
|
||||
}
|
||||
|
||||
static int aics_client_common_control(struct bt_conn *conn, uint8_t opcode, struct bt_aics *inst)
|
||||
{
|
||||
int err;
|
||||
|
||||
if (!conn) {
|
||||
return -ENOTCONN;
|
||||
} else if (!inst->cli.control_handle) {
|
||||
BT_DBG("Handle not set for opcode %u", opcode);
|
||||
return -EINVAL;
|
||||
} else if (inst->cli.busy) {
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
inst->cli.cp_val.cp.opcode = opcode;
|
||||
inst->cli.cp_val.cp.counter = inst->cli.change_counter;
|
||||
|
||||
inst->cli.write_params.offset = 0;
|
||||
inst->cli.write_params.data = &inst->cli.cp_val.cp;
|
||||
inst->cli.write_params.length = sizeof(inst->cli.cp_val.cp);
|
||||
inst->cli.write_params.handle = inst->cli.control_handle;
|
||||
inst->cli.write_params.func = aics_client_write_aics_cp_cb;
|
||||
|
||||
err = bt_gatt_write(conn, &inst->cli.write_params);
|
||||
if (!err) {
|
||||
inst->cli.busy = true;
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
|
||||
static uint8_t aics_client_read_desc_cb(struct bt_conn *conn, uint8_t err,
|
||||
struct bt_gatt_read_params *params,
|
||||
const void *data, uint16_t length)
|
||||
{
|
||||
int cb_err = err;
|
||||
char desc[MIN(CONFIG_BT_L2CAP_RX_MTU, BT_ATT_MAX_ATTRIBUTE_LEN) + 1];
|
||||
struct bt_aics *inst = lookup_aics_by_handle(conn, params->single.handle);
|
||||
|
||||
memset(params, 0, sizeof(*params));
|
||||
|
||||
if (!inst) {
|
||||
BT_DBG("Instance not found");
|
||||
return BT_GATT_ITER_STOP;
|
||||
}
|
||||
|
||||
inst->cli.busy = false;
|
||||
|
||||
if (cb_err) {
|
||||
BT_DBG("Description read failed: %d", err);
|
||||
if (inst->cli.cb && inst->cli.cb->description) {
|
||||
inst->cli.cb->description(conn, inst, cb_err, NULL);
|
||||
}
|
||||
return BT_GATT_ITER_STOP;
|
||||
}
|
||||
|
||||
if (data) {
|
||||
BT_HEXDUMP_DBG(data, length, "Input description read");
|
||||
|
||||
/* Truncate if too large */
|
||||
if (length > sizeof(desc) - 1) {
|
||||
BT_DBG("Description truncated from %u to %zu octets",
|
||||
length, sizeof(desc) - 1);
|
||||
}
|
||||
length = MIN(sizeof(desc) - 1, length);
|
||||
|
||||
/* TODO: Handle long reads */
|
||||
|
||||
memcpy(desc, data, length);
|
||||
}
|
||||
|
||||
desc[length] = '\0';
|
||||
BT_DBG("Input description: %s", log_strdup(desc));
|
||||
|
||||
if (inst->cli.cb && inst->cli.cb->description) {
|
||||
inst->cli.cb->description(conn, inst, cb_err, desc);
|
||||
}
|
||||
|
||||
return BT_GATT_ITER_STOP;
|
||||
}
|
||||
|
||||
static bool valid_inst_discovered(struct bt_aics *inst)
|
||||
{
|
||||
return inst->cli.state_handle &&
|
||||
inst->cli.gain_handle &&
|
||||
inst->cli.type_handle &&
|
||||
inst->cli.status_handle &&
|
||||
inst->cli.control_handle &&
|
||||
inst->cli.desc_handle;
|
||||
}
|
||||
|
||||
static uint8_t aics_discover_func(struct bt_conn *conn, const struct bt_gatt_attr *attr,
|
||||
struct bt_gatt_discover_params *params)
|
||||
{
|
||||
struct bt_aics *inst = (struct bt_aics *)CONTAINER_OF(
|
||||
params, struct bt_aics_client, discover_params);
|
||||
|
||||
if (!attr) {
|
||||
BT_DBG("Discovery complete for AICS %p", inst);
|
||||
|
||||
memset(params, 0, sizeof(*params));
|
||||
|
||||
inst->cli.busy = false;
|
||||
|
||||
if (inst->cli.cb && inst->cli.cb->discover) {
|
||||
int err = valid_inst_discovered(inst) ? 0 : -ENOENT;
|
||||
|
||||
inst->cli.cb->discover(conn, inst, err);
|
||||
}
|
||||
|
||||
return BT_GATT_ITER_STOP;
|
||||
}
|
||||
|
||||
BT_DBG("[ATTRIBUTE] handle 0x%04X", attr->handle);
|
||||
|
||||
if (params->type == BT_GATT_DISCOVER_CHARACTERISTIC) {
|
||||
struct bt_gatt_subscribe_params *sub_params = NULL;
|
||||
struct bt_gatt_chrc *chrc;
|
||||
|
||||
chrc = (struct bt_gatt_chrc *)attr->user_data;
|
||||
if (inst->cli.start_handle == 0) {
|
||||
inst->cli.start_handle = chrc->value_handle;
|
||||
}
|
||||
inst->cli.end_handle = chrc->value_handle;
|
||||
|
||||
if (!bt_uuid_cmp(chrc->uuid, BT_UUID_AICS_STATE)) {
|
||||
BT_DBG("Audio Input state");
|
||||
inst->cli.state_handle = chrc->value_handle;
|
||||
sub_params = &inst->cli.state_sub_params;
|
||||
} else if (!bt_uuid_cmp(chrc->uuid, BT_UUID_AICS_GAIN_SETTINGS)) {
|
||||
BT_DBG("Gain settings");
|
||||
inst->cli.gain_handle = chrc->value_handle;
|
||||
} else if (!bt_uuid_cmp(chrc->uuid, BT_UUID_AICS_INPUT_TYPE)) {
|
||||
BT_DBG("Input type");
|
||||
inst->cli.type_handle = chrc->value_handle;
|
||||
} else if (!bt_uuid_cmp(chrc->uuid, BT_UUID_AICS_INPUT_STATUS)) {
|
||||
BT_DBG("Input status");
|
||||
inst->cli.status_handle = chrc->value_handle;
|
||||
sub_params = &inst->cli.status_sub_params;
|
||||
} else if (!bt_uuid_cmp(chrc->uuid, BT_UUID_AICS_CONTROL)) {
|
||||
BT_DBG("Control point");
|
||||
inst->cli.control_handle = chrc->value_handle;
|
||||
} else if (!bt_uuid_cmp(chrc->uuid, BT_UUID_AICS_DESCRIPTION)) {
|
||||
BT_DBG("Description");
|
||||
inst->cli.desc_handle = chrc->value_handle;
|
||||
if (chrc->properties & BT_GATT_CHRC_NOTIFY) {
|
||||
sub_params = &inst->cli.desc_sub_params;
|
||||
}
|
||||
|
||||
if (chrc->properties & BT_GATT_CHRC_WRITE_WITHOUT_RESP) {
|
||||
inst->cli.desc_writable = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (sub_params) {
|
||||
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 = aics_client_notify_handler;
|
||||
bt_gatt_subscribe(conn, sub_params);
|
||||
}
|
||||
}
|
||||
|
||||
return BT_GATT_ITER_CONTINUE;
|
||||
}
|
||||
|
||||
static void aics_client_reset(struct bt_aics *inst, struct bt_conn *conn)
|
||||
{
|
||||
inst->cli.desc_writable = 0;
|
||||
inst->cli.change_counter = 0;
|
||||
inst->cli.gain_mode = 0;
|
||||
inst->cli.start_handle = 0;
|
||||
inst->cli.end_handle = 0;
|
||||
inst->cli.state_handle = 0;
|
||||
inst->cli.gain_handle = 0;
|
||||
inst->cli.type_handle = 0;
|
||||
inst->cli.status_handle = 0;
|
||||
inst->cli.control_handle = 0;
|
||||
inst->cli.desc_handle = 0;
|
||||
|
||||
/* It's okay if these fail */
|
||||
(void)bt_gatt_unsubscribe(conn, &inst->cli.state_sub_params);
|
||||
(void)bt_gatt_unsubscribe(conn, &inst->cli.status_sub_params);
|
||||
(void)bt_gatt_unsubscribe(conn, &inst->cli.desc_sub_params);
|
||||
}
|
||||
|
||||
|
||||
int bt_aics_discover(struct bt_conn *conn, struct bt_aics *inst,
|
||||
const struct bt_aics_discover_param *param)
|
||||
{
|
||||
int err = 0;
|
||||
|
||||
CHECKIF(!inst || !conn || !param) {
|
||||
BT_DBG("%s cannot be NULL",
|
||||
inst == NULL ? "inst" : conn == NULL ? "conn" : "param");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
CHECKIF(param->end_handle <= param->start_handle) {
|
||||
BT_DBG("start_handle (%u) shall be less than end_handle (%u)",
|
||||
param->start_handle, param->end_handle);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
CHECKIF(!inst->cli.active) {
|
||||
BT_DBG("Inactive instance");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (inst->cli.busy) {
|
||||
BT_DBG("Instance is busy");
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
aics_client_reset(inst, conn);
|
||||
|
||||
(void)memset(&inst->cli.discover_params, 0, sizeof(inst->cli.discover_params));
|
||||
inst->cli.conn = conn;
|
||||
inst->cli.discover_params.start_handle = param->start_handle;
|
||||
inst->cli.discover_params.end_handle = param->end_handle;
|
||||
inst->cli.discover_params.type = BT_GATT_DISCOVER_CHARACTERISTIC;
|
||||
inst->cli.discover_params.func = aics_discover_func;
|
||||
|
||||
err = bt_gatt_discover(conn, &inst->cli.discover_params);
|
||||
if (err) {
|
||||
BT_DBG("Discover failed (err %d)", err);
|
||||
} else {
|
||||
inst->cli.busy = true;
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
struct bt_aics *bt_aics_client_free_instance_get(void)
|
||||
{
|
||||
for (int i = 0; i < ARRAY_SIZE(aics_insts); i++) {
|
||||
if (!aics_insts[i].cli.active) {
|
||||
aics_insts[i].cli.active = true;
|
||||
return &aics_insts[i];
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int bt_aics_client_state_get(struct bt_conn *conn, struct bt_aics *inst)
|
||||
{
|
||||
int err;
|
||||
|
||||
CHECKIF(!conn) {
|
||||
BT_DBG("NULL conn");
|
||||
return -ENOTCONN;
|
||||
}
|
||||
|
||||
CHECKIF(!inst) {
|
||||
BT_DBG("NULL instance");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (!inst->cli.state_handle) {
|
||||
BT_DBG("Handle not set");
|
||||
return -EINVAL;
|
||||
} else if (inst->cli.busy) {
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
inst->cli.read_params.func = aics_client_read_state_cb;
|
||||
inst->cli.read_params.handle_count = 1;
|
||||
inst->cli.read_params.single.handle = inst->cli.state_handle;
|
||||
|
||||
err = bt_gatt_read(conn, &inst->cli.read_params);
|
||||
if (!err) {
|
||||
inst->cli.busy = true;
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
int bt_aics_client_gain_setting_get(struct bt_conn *conn, struct bt_aics *inst)
|
||||
{
|
||||
int err;
|
||||
|
||||
CHECKIF(!conn) {
|
||||
BT_DBG("NULL conn");
|
||||
return -ENOTCONN;
|
||||
}
|
||||
|
||||
CHECKIF(!inst) {
|
||||
BT_DBG("NULL instance");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (!inst->cli.gain_handle) {
|
||||
BT_DBG("Handle not set");
|
||||
return -EINVAL;
|
||||
} else if (inst->cli.busy) {
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
inst->cli.read_params.func = aics_client_read_gain_settings_cb;
|
||||
inst->cli.read_params.handle_count = 1;
|
||||
inst->cli.read_params.single.handle = inst->cli.gain_handle;
|
||||
|
||||
err = bt_gatt_read(conn, &inst->cli.read_params);
|
||||
if (!err) {
|
||||
inst->cli.busy = true;
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
int bt_aics_client_type_get(struct bt_conn *conn, struct bt_aics *inst)
|
||||
{
|
||||
int err;
|
||||
|
||||
CHECKIF(!conn) {
|
||||
BT_DBG("NULL conn");
|
||||
return -ENOTCONN;
|
||||
}
|
||||
|
||||
CHECKIF(!inst) {
|
||||
BT_DBG("NULL instance");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (!inst->cli.type_handle) {
|
||||
BT_DBG("Handle not set");
|
||||
return -EINVAL;
|
||||
} else if (inst->cli.busy) {
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
inst->cli.read_params.func = aics_client_read_type_cb;
|
||||
inst->cli.read_params.handle_count = 1;
|
||||
inst->cli.read_params.single.handle = inst->cli.type_handle;
|
||||
|
||||
err = bt_gatt_read(conn, &inst->cli.read_params);
|
||||
if (!err) {
|
||||
inst->cli.busy = true;
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
int bt_aics_client_status_get(struct bt_conn *conn, struct bt_aics *inst)
|
||||
{
|
||||
int err;
|
||||
|
||||
CHECKIF(!conn) {
|
||||
BT_DBG("NULL conn");
|
||||
return -ENOTCONN;
|
||||
}
|
||||
|
||||
CHECKIF(!inst) {
|
||||
BT_DBG("NULL instance");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (!inst->cli.status_handle) {
|
||||
BT_DBG("Handle not set");
|
||||
return -EINVAL;
|
||||
} else if (inst->cli.busy) {
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
inst->cli.read_params.func = aics_client_read_status_cb;
|
||||
inst->cli.read_params.handle_count = 1;
|
||||
inst->cli.read_params.single.handle = inst->cli.status_handle;
|
||||
|
||||
err = bt_gatt_read(conn, &inst->cli.read_params);
|
||||
if (!err) {
|
||||
inst->cli.busy = true;
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
int bt_aics_client_unmute(struct bt_conn *conn, struct bt_aics *inst)
|
||||
{
|
||||
return aics_client_common_control(conn, BT_AICS_OPCODE_UNMUTE, inst);
|
||||
}
|
||||
|
||||
int bt_aics_client_mute(struct bt_conn *conn, struct bt_aics *inst)
|
||||
{
|
||||
return aics_client_common_control(conn, BT_AICS_OPCODE_MUTE, inst);
|
||||
}
|
||||
|
||||
int bt_aics_client_manual_gain_set(struct bt_conn *conn, struct bt_aics *inst)
|
||||
{
|
||||
return aics_client_common_control(conn, BT_AICS_OPCODE_SET_MANUAL, inst);
|
||||
}
|
||||
|
||||
int bt_aics_client_automatic_gain_set(struct bt_conn *conn, struct bt_aics *inst)
|
||||
{
|
||||
return aics_client_common_control(conn, BT_AICS_OPCODE_SET_AUTO, inst);
|
||||
}
|
||||
|
||||
int bt_aics_client_gain_set(struct bt_conn *conn, struct bt_aics *inst, int8_t gain)
|
||||
{
|
||||
int err;
|
||||
|
||||
CHECKIF(!conn) {
|
||||
BT_DBG("NULL conn");
|
||||
return -ENOTCONN;
|
||||
}
|
||||
|
||||
CHECKIF(!inst) {
|
||||
BT_DBG("NULL instance");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (!inst->cli.control_handle) {
|
||||
BT_DBG("Handle not set");
|
||||
return -EINVAL;
|
||||
} else if (inst->cli.busy) {
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
inst->cli.cp_val.cp.opcode = BT_AICS_OPCODE_SET_GAIN;
|
||||
inst->cli.cp_val.cp.counter = inst->cli.change_counter;
|
||||
inst->cli.cp_val.gain_setting = gain;
|
||||
|
||||
inst->cli.write_params.data = &inst->cli.cp_val;
|
||||
inst->cli.write_params.length = sizeof(inst->cli.cp_val);
|
||||
inst->cli.write_params.handle = inst->cli.control_handle;
|
||||
inst->cli.write_params.func = aics_client_write_aics_cp_cb;
|
||||
|
||||
err = bt_gatt_write(conn, &inst->cli.write_params);
|
||||
if (!err) {
|
||||
inst->cli.busy = true;
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
int bt_aics_client_description_get(struct bt_conn *conn, struct bt_aics *inst)
|
||||
{
|
||||
int err;
|
||||
|
||||
CHECKIF(!conn) {
|
||||
BT_DBG("NULL conn");
|
||||
return -ENOTCONN;
|
||||
}
|
||||
|
||||
CHECKIF(!inst) {
|
||||
BT_DBG("NULL instance");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (!inst->cli.desc_handle) {
|
||||
BT_DBG("Handle not set");
|
||||
return -EINVAL;
|
||||
} else if (inst->cli.busy) {
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
inst->cli.read_params.func = aics_client_read_desc_cb;
|
||||
inst->cli.read_params.handle_count = 1;
|
||||
inst->cli.read_params.single.handle = inst->cli.desc_handle;
|
||||
|
||||
err = bt_gatt_read(conn, &inst->cli.read_params);
|
||||
if (!err) {
|
||||
inst->cli.busy = true;
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
int bt_aics_client_description_set(struct bt_conn *conn, struct bt_aics *inst,
|
||||
const char *description)
|
||||
{
|
||||
CHECKIF(!conn) {
|
||||
BT_DBG("NULL conn");
|
||||
return -ENOTCONN;
|
||||
}
|
||||
|
||||
CHECKIF(!inst) {
|
||||
BT_DBG("NULL instance");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (!inst->cli.desc_handle) {
|
||||
BT_DBG("Handle not set");
|
||||
return -EINVAL;
|
||||
} else if (inst->cli.busy) {
|
||||
return -EBUSY;
|
||||
} else if (!inst->cli.desc_writable) {
|
||||
BT_DBG("Description is not writable on peer service instance");
|
||||
return -EPERM;
|
||||
}
|
||||
|
||||
return bt_gatt_write_without_response(conn, inst->cli.desc_handle,
|
||||
description, strlen(description),
|
||||
false);
|
||||
}
|
||||
|
||||
void bt_aics_client_cb_register(struct bt_aics *inst, struct bt_aics_cb *cb)
|
||||
{
|
||||
CHECKIF(!inst) {
|
||||
BT_DBG("inst cannot be NULL");
|
||||
return;
|
||||
}
|
||||
|
||||
inst->cli.cb = cb;
|
||||
}
|
132
subsys/bluetooth/audio/aics_internal.h
Normal file
132
subsys/bluetooth/audio/aics_internal.h
Normal file
|
@ -0,0 +1,132 @@
|
|||
/** @file
|
||||
* @brief Internal APIs for Bluetooth AICS.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright (c) 2020 Bose Corporation
|
||||
* Copyright (c) 2020 Nordic Semiconductor ASA
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#ifndef ZEPHYR_INCLUDE_BLUETOOTH_AUDIO_AICS_INTERNAL_
|
||||
#define ZEPHYR_INCLUDE_BLUETOOTH_AUDIO_AICS_INTERNAL_
|
||||
#include <zephyr/types.h>
|
||||
#include <bluetooth/gatt.h>
|
||||
|
||||
#if defined(CONFIG_BT_AICS)
|
||||
#define BT_AICS_MAX_DESC_SIZE CONFIG_BT_AICS_MAX_INPUT_DESCRIPTION_SIZE
|
||||
#else
|
||||
#define BT_AICS_MAX_DESC_SIZE 1
|
||||
#endif /* CONFIG_BT_AICS */
|
||||
|
||||
/* AICS opcodes */
|
||||
#define BT_AICS_OPCODE_SET_GAIN 0x01
|
||||
#define BT_AICS_OPCODE_UNMUTE 0x02
|
||||
#define BT_AICS_OPCODE_MUTE 0x03
|
||||
#define BT_AICS_OPCODE_SET_MANUAL 0x04
|
||||
#define BT_AICS_OPCODE_SET_AUTO 0x05
|
||||
|
||||
/* AICS status */
|
||||
#define BT_AICS_STATUS_INACTIVE 0x00
|
||||
#define BT_AICS_STATUS_ACTIVE 0x01
|
||||
|
||||
#define BT_AICS_INPUT_MODE_IMMUTABLE(gain_mode) \
|
||||
((gain_mode) == BT_AICS_MODE_MANUAL_ONLY || (gain_mode) == BT_AICS_MODE_AUTO_ONLY)
|
||||
|
||||
#define BT_AICS_INPUT_MODE_SETTABLE(gain_mode) \
|
||||
((gain_mode) == BT_AICS_MODE_AUTO || (gain_mode) == BT_AICS_MODE_MANUAL)
|
||||
|
||||
struct bt_aics_control {
|
||||
uint8_t opcode;
|
||||
uint8_t counter;
|
||||
} __packed;
|
||||
|
||||
struct bt_aics_gain_control {
|
||||
struct bt_aics_control cp;
|
||||
int8_t gain_setting;
|
||||
} __packed;
|
||||
|
||||
struct bt_aics_client {
|
||||
uint8_t change_counter;
|
||||
uint8_t gain_mode;
|
||||
bool desc_writable;
|
||||
bool active;
|
||||
|
||||
uint16_t start_handle;
|
||||
uint16_t end_handle;
|
||||
uint16_t state_handle;
|
||||
uint16_t gain_handle;
|
||||
uint16_t type_handle;
|
||||
uint16_t status_handle;
|
||||
uint16_t control_handle;
|
||||
uint16_t desc_handle;
|
||||
struct bt_gatt_subscribe_params state_sub_params;
|
||||
struct bt_gatt_subscribe_params status_sub_params;
|
||||
struct bt_gatt_subscribe_params desc_sub_params;
|
||||
uint8_t subscribe_cnt;
|
||||
bool cp_retried;
|
||||
|
||||
bool busy;
|
||||
struct bt_aics_gain_control 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_aics_cb *cb;
|
||||
struct bt_conn *conn;
|
||||
};
|
||||
|
||||
struct bt_aics_state {
|
||||
int8_t gain;
|
||||
uint8_t mute;
|
||||
uint8_t gain_mode;
|
||||
uint8_t change_counter;
|
||||
} __packed;
|
||||
|
||||
struct bt_aics_gain_settings {
|
||||
uint8_t units;
|
||||
int8_t minimum;
|
||||
int8_t maximum;
|
||||
} __packed;
|
||||
|
||||
struct bt_aics_server {
|
||||
struct bt_aics_state state;
|
||||
struct bt_aics_gain_settings gain_settings;
|
||||
bool initialized;
|
||||
uint8_t type;
|
||||
uint8_t status;
|
||||
struct bt_aics *inst;
|
||||
char description[BT_AICS_MAX_DESC_SIZE];
|
||||
struct bt_aics_cb *cb;
|
||||
|
||||
struct bt_gatt_service *service_p;
|
||||
};
|
||||
|
||||
/* Struct used as a common type for the api */
|
||||
struct bt_aics {
|
||||
union {
|
||||
struct bt_aics_server srv;
|
||||
struct bt_aics_client cli;
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
uint8_t aics_client_notify_handler(struct bt_conn *conn,
|
||||
struct bt_gatt_subscribe_params *params,
|
||||
const void *data, uint16_t length);
|
||||
int bt_aics_client_register(struct bt_aics *inst);
|
||||
int bt_aics_client_unregister(struct bt_aics *inst);
|
||||
int bt_aics_client_state_get(struct bt_conn *conn, struct bt_aics *inst);
|
||||
int bt_aics_client_gain_setting_get(struct bt_conn *conn, struct bt_aics *inst);
|
||||
int bt_aics_client_type_get(struct bt_conn *conn, struct bt_aics *inst);
|
||||
int bt_aics_client_status_get(struct bt_conn *conn, struct bt_aics *inst);
|
||||
int bt_aics_client_unmute(struct bt_conn *conn, struct bt_aics *inst);
|
||||
int bt_aics_client_mute(struct bt_conn *conn, struct bt_aics *inst);
|
||||
int bt_aics_client_manual_gain_set(struct bt_conn *conn, struct bt_aics *inst);
|
||||
int bt_aics_client_automatic_gain_set(struct bt_conn *conn, struct bt_aics *inst);
|
||||
int bt_aics_client_gain_set(struct bt_conn *conn, struct bt_aics *inst, int8_t gain);
|
||||
int bt_aics_client_description_get(struct bt_conn *conn, struct bt_aics *inst);
|
||||
int bt_aics_client_description_set(struct bt_conn *conn, struct bt_aics *inst,
|
||||
const char *description);
|
||||
|
||||
#endif /* ZEPHYR_INCLUDE_BLUETOOTH_AUDIO_AICS_INTERNAL_ */
|
|
@ -50,5 +50,9 @@ CONFIG_BT_AUTO_PHY_UPDATE=y
|
|||
CONFIG_BT_AUDIO=y
|
||||
CONFIG_BT_AUDIO_UNICAST=y
|
||||
CONFIG_BT_AUDIO_BROADCAST=y
|
||||
|
||||
CONFIG_BT_VOCS_MAX_INSTANCE_COUNT=1
|
||||
CONFIG_BT_VOCS_CLIENT_MAX_INSTANCE_COUNT=1
|
||||
|
||||
CONFIG_BT_AICS_MAX_INSTANCE_COUNT=1
|
||||
CONFIG_BT_AICS_CLIENT_MAX_INSTANCE_COUNT=1
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue