Bluetooth: has: Handle active preset selection

This adds handling of active preset selection in HAS.

Signed-off-by: Mariusz Skamra <mariusz.skamra@codecoup.pl>
This commit is contained in:
Mariusz Skamra 2022-04-27 14:50:32 +02:00 committed by Carles Cufí
commit 4e15cbe456
2 changed files with 262 additions and 3 deletions

View file

@ -213,6 +213,26 @@ int bt_has_client_conn_get(const struct bt_has *has, struct bt_conn **conn);
*/ */
int bt_has_client_presets_read(struct bt_has *has, uint8_t index, uint8_t max_count); int bt_has_client_presets_read(struct bt_has *has, uint8_t index, uint8_t max_count);
/** @brief Preset operations structure. */
struct bt_has_preset_ops {
/**
* @brief Preset select callback.
*
* This callback is called when the client requests to select preset identified by
* @p index.
*
* @param index Preset index requested to activate.
* @param sync Whether the server must relay this change to the other member of the
* Binaural Hearing Aid Set.
*
* @return 0 in case of success or negative value in case of error.
* @return -EBUSY if operation cannot be performed at the time.
* @return -EINPROGRESS in case where user has to confirm once the requested preset
* becomes active by calling @ref bt_has_preset_active_set.
*/
int (*select)(uint8_t index, bool sync);
};
/** @brief Register structure for preset. */ /** @brief Register structure for preset. */
struct bt_has_preset_register_param { struct bt_has_preset_register_param {
/** /**
@ -238,6 +258,9 @@ struct bt_has_preset_register_param {
* @ref BT_HAS_PRESET_NAME_MAX, the name will be truncated. * @ref BT_HAS_PRESET_NAME_MAX, the name will be truncated.
*/ */
const char *name; const char *name;
/** Preset operations structure. */
const struct bt_has_preset_ops *ops;
}; };
/** /**
@ -318,6 +341,39 @@ typedef uint8_t (*bt_has_preset_func_t)(uint8_t index, enum bt_has_properties pr
*/ */
void bt_has_preset_foreach(uint8_t index, bt_has_preset_func_t func, void *user_data); void bt_has_preset_foreach(uint8_t index, bt_has_preset_func_t func, void *user_data);
/**
* @brief Set active preset.
*
* Function used to set the preset identified by the @p index as the active preset.
* The preset index will be notified to peer devices.
*
* @param index Preset index.
*
* @return 0 in case of success or negative value in case of error.
*/
int bt_has_preset_active_set(uint8_t index);
/**
* @brief Get active preset.
*
* Function used to get the currently active preset index.
*
* @return Active preset index.
*/
uint8_t bt_has_preset_active_get(void);
/**
* @brief Clear out active preset.
*
* Used by server to deactivate currently active preset.
*
* @return 0 in case of success or negative value in case of error.
*/
static inline int bt_has_preset_active_clear(void)
{
return bt_has_preset_active_set(BT_HAS_PRESET_INDEX_NONE);
}
#ifdef __cplusplus #ifdef __cplusplus
} }
#endif #endif

View file

@ -89,6 +89,7 @@ BT_GATT_SERVICE_DEFINE(has_svc,
#if defined(CONFIG_BT_HAS_PRESET_SUPPORT) #if defined(CONFIG_BT_HAS_PRESET_SUPPORT)
#define PRESET_CONTROL_POINT_ATTR &has_svc.attrs[4] #define PRESET_CONTROL_POINT_ATTR &has_svc.attrs[4]
#define ACTIVE_PRESET_INDEX_ATTR &has_svc.attrs[7]
static struct has_client { static struct has_client {
struct bt_conn *conn; struct bt_conn *conn;
@ -113,11 +114,15 @@ static struct has_preset {
#else #else
const char *name; const char *name;
#endif /* CONFIG_BT_HAS_PRESET_NAME_DYNAMIC */ #endif /* CONFIG_BT_HAS_PRESET_NAME_DYNAMIC */
const struct bt_has_preset_ops *ops;
} has_preset_list[CONFIG_BT_HAS_PRESET_COUNT]; } has_preset_list[CONFIG_BT_HAS_PRESET_COUNT];
/* Number of registered presets */ /* Number of registered presets */
static uint8_t has_preset_num; static uint8_t has_preset_num;
/* Active preset notification work */
static struct k_work active_preset_work;
static void process_control_point_work(struct k_work *work); static void process_control_point_work(struct k_work *work);
static struct has_client *client_get_or_new(struct bt_conn *conn) static struct has_client *client_get_or_new(struct bt_conn *conn)
@ -240,7 +245,7 @@ static int preset_index_compare(const void *p1, const void *p2)
} }
static struct has_preset *preset_alloc(uint8_t index, enum bt_has_properties properties, static struct has_preset *preset_alloc(uint8_t index, enum bt_has_properties properties,
const char *name) const char *name, const struct bt_has_preset_ops *ops)
{ {
struct has_preset *preset = NULL; struct has_preset *preset = NULL;
@ -253,6 +258,7 @@ static struct has_preset *preset_alloc(uint8_t index, enum bt_has_properties pro
#else #else
preset->name = name; preset->name = name;
#endif /* CONFIG_BT_HAS_PRESET_NAME_DYNAMIC */ #endif /* CONFIG_BT_HAS_PRESET_NAME_DYNAMIC */
preset->ops = ops;
has_preset_num++; has_preset_num++;
@ -513,6 +519,150 @@ static uint8_t handle_read_preset_req(struct bt_conn *conn, struct net_buf_simpl
return 0; return 0;
} }
static void active_preset_work_process(struct k_work *work)
{
const uint8_t active_index = bt_has_preset_active_get();
bt_gatt_notify(NULL, ACTIVE_PRESET_INDEX_ATTR, &active_index, sizeof(active_index));
}
static void preset_active_set(uint8_t index)
{
if (index != has.active_index) {
has.active_index = index;
/* Emit active preset notification */
k_work_submit(&active_preset_work);
}
}
static uint8_t preset_select(const struct has_preset *preset, bool sync)
{
const int err = preset->ops->select(preset->index, sync);
if (err == -EINPROGRESS) {
/* User has to confirm once the requested preset becomes active by
* calling bt_has_preset_active_set.
*/
return 0;
}
if (err == -EBUSY) {
return BT_HAS_ERR_OPERATION_NOT_POSSIBLE;
}
if (err) {
return BT_ATT_ERR_UNLIKELY;
}
preset_active_set(preset->index);
return 0;
}
static uint8_t handle_set_active_preset(struct net_buf_simple *buf, bool sync)
{
const struct bt_has_cp_set_active_preset *pdu;
const struct has_preset *preset = NULL;
if (buf->len < sizeof(*pdu)) {
return BT_HAS_ERR_INVALID_PARAM_LEN;
}
pdu = net_buf_simple_pull_mem(buf, sizeof(*pdu));
preset_foreach(pdu->index, pdu->index, preset_found, &preset);
if (preset == NULL) {
return BT_ATT_ERR_OUT_OF_RANGE;
}
if (!(preset->properties & BT_HAS_PROP_AVAILABLE)) {
return BT_HAS_ERR_OPERATION_NOT_POSSIBLE;
}
return preset_select(preset, sync);
}
static uint8_t handle_set_next_preset(bool sync)
{
const struct has_preset *next_avail = NULL;
const struct has_preset *first_avail = NULL;
for (size_t i = 0; i < has_preset_num; i++) {
const struct has_preset *tmp = &has_preset_list[i];
if (tmp->index == BT_HAS_PRESET_INDEX_NONE) {
break;
}
if (!(tmp->properties & BT_HAS_PROP_AVAILABLE)) {
continue;
}
if (tmp->index < has.active_index && !first_avail) {
first_avail = tmp;
continue;
}
if (tmp->index > has.active_index) {
next_avail = tmp;
break;
}
}
if (next_avail) {
return preset_select(next_avail, sync);
}
if (first_avail) {
return preset_select(first_avail, sync);
}
return BT_HAS_ERR_OPERATION_NOT_POSSIBLE;
}
static uint8_t handle_set_prev_preset(bool sync)
{
const struct has_preset *prev_available = NULL;
const struct has_preset *last_available = NULL;
for (size_t i = 0; i < ARRAY_SIZE(has_preset_list); i++) {
const struct has_preset *tmp = &has_preset_list[i];
if (tmp->index == BT_HAS_PRESET_INDEX_NONE) {
break;
}
if (!(tmp->properties & BT_HAS_PROP_AVAILABLE)) {
continue;
}
if (tmp->index < has.active_index) {
prev_available = tmp;
continue;
}
if (prev_available) {
break;
}
if (tmp->index > has.active_index) {
last_available = tmp;
continue;
}
}
if (prev_available) {
return preset_select(prev_available, sync);
}
if (last_available) {
return preset_select(last_available, sync);
}
return BT_HAS_ERR_OPERATION_NOT_POSSIBLE;
}
static uint8_t handle_control_point_op(struct bt_conn *conn, struct net_buf_simple *buf) static uint8_t handle_control_point_op(struct bt_conn *conn, struct net_buf_simple *buf)
{ {
const struct bt_has_cp_hdr *hdr; const struct bt_has_cp_hdr *hdr;
@ -525,7 +675,21 @@ static uint8_t handle_control_point_op(struct bt_conn *conn, struct net_buf_simp
switch (hdr->opcode) { switch (hdr->opcode) {
case BT_HAS_OP_READ_PRESET_REQ: case BT_HAS_OP_READ_PRESET_REQ:
return handle_read_preset_req(conn, buf); return handle_read_preset_req(conn, buf);
} case BT_HAS_OP_SET_ACTIVE_PRESET:
return handle_set_active_preset(buf, false);
case BT_HAS_OP_SET_NEXT_PRESET:
return handle_set_next_preset(false);
case BT_HAS_OP_SET_PREV_PRESET:
return handle_set_prev_preset(false);
#if defined(CONFIG_BT_HAS_PRESET_SYNC_SUPPORT)
case BT_HAS_OP_SET_ACTIVE_PRESET_SYNC:
return handle_set_active_preset(buf, true);
case BT_HAS_OP_SET_NEXT_PRESET_SYNC:
return handle_set_next_preset(true);
case BT_HAS_OP_SET_PREV_PRESET_SYNC:
return handle_set_prev_preset(true);
#endif /* CONFIG_BT_HAS_PRESET_SYNC_SUPPORT */
};
return BT_HAS_ERR_INVALID_OPCODE; return BT_HAS_ERR_INVALID_OPCODE;
} }
@ -589,12 +753,22 @@ int bt_has_preset_register(const struct bt_has_preset_register_param *param)
BT_WARN("param->name is too long (%zu > %u)", name_len, BT_HAS_PRESET_NAME_MAX); BT_WARN("param->name is too long (%zu > %u)", name_len, BT_HAS_PRESET_NAME_MAX);
} }
CHECKIF(param->ops == NULL) {
BT_ERR("param->ops is NULL");
return -EINVAL;
}
CHECKIF(param->ops->select == NULL) {
BT_ERR("param->ops->select is NULL");
return -EINVAL;
}
preset_foreach(param->index, param->index, preset_found, &preset); preset_foreach(param->index, param->index, preset_found, &preset);
if (preset != NULL) { if (preset != NULL) {
return -EALREADY; return -EALREADY;
} }
preset = preset_alloc(param->index, param->properties, param->name); preset = preset_alloc(param->index, param->properties, param->name, param->ops);
if (preset == NULL) { if (preset == NULL) {
return -ENOMEM; return -ENOMEM;
} }
@ -706,6 +880,31 @@ void bt_has_preset_foreach(uint8_t index, bt_has_preset_func_t func, void *user_
preset_foreach(start_index, end_index, bt_has_preset_foreach_func, &data); preset_foreach(start_index, end_index, bt_has_preset_foreach_func, &data);
} }
int bt_has_preset_active_set(uint8_t index)
{
if (index != BT_HAS_PRESET_INDEX_NONE) {
struct has_preset *preset = NULL;
preset_foreach(index, index, preset_found, &preset);
if (preset == NULL) {
return -ENOENT;
}
if (!(preset->properties & BT_HAS_PROP_AVAILABLE)) {
return -EINVAL;
}
}
preset_active_set(index);
return 0;
}
uint8_t bt_has_preset_active_get(void)
{
return has.active_index;
}
#endif /* CONFIG_BT_HAS_PRESET_SUPPORT */ #endif /* CONFIG_BT_HAS_PRESET_SUPPORT */
static int has_init(const struct device *dev) static int has_init(const struct device *dev)
@ -748,6 +947,10 @@ static int has_init(const struct device *dev)
} }
} }
#if defined(CONFIG_BT_HAS_PRESET_SUPPORT)
k_work_init(&active_preset_work, active_preset_work_process);
#endif /* CONFIG_BT_HAS_PRESET_SUPPORT */
return 0; return 0;
} }