Bluetooth: has: Add stubs for handling control point requests

This adds server stubs for handling the preset control point requests.

Signed-off-by: Mariusz Skamra <mariusz.skamra@codecoup.pl>
This commit is contained in:
Mariusz Skamra 2022-03-21 16:13:22 +01:00 committed by Carles Cufí
commit f6d0dc355f
3 changed files with 238 additions and 1 deletions

View file

@ -55,6 +55,41 @@ config BT_HAS_HEARING_AID_TYPE
help help
The value shall be one of 3 defined by the HAS 1.0 specification table 3.2 The value shall be one of 3 defined by the HAS 1.0 specification table 3.2
config BT_HAS_PRESET_COUNT
int "Preset record list size"
default 2
range 0 255
help
This option sets the number of Hearing Access Service Presets
that can be registered. Setting this value to 0 disables Presets support.
if BT_HAS_PRESET_COUNT > 0
if BT_HAS_HEARING_AID_BINAURAL
config BT_HAS_IDENTICAL_PRESET_RECORDS
bool "Identical preset records in Binaural Hearing Aid Set"
help
Set if the list of preset records is identical to the list of preset records
on the other member in the Binaural Hearing Aid Set.
This option sets Independent Presets field in Hearing Aid Features to 0b0.
config BT_HAS_PRESET_SYNC_SUPPORT
bool "Preset synchronization support"
depends on BT_HAS_IDENTICAL_PRESET_RECORDS
help
Set if the hearing aid has support for relaying active preset changes to the other
member in the Binaural Hearing Aid Set.
endif # BT_HAS_HEARING_AID_BINAURAL
config BT_HAS_PRESET_NAME_DYNAMIC
bool "Allow to set preset name on runtime"
help
Enabling this option allows for runtime configuration of preset name.
endif # BT_HAS_PRESET_COUNT > 0
config BT_DEBUG_HAS config BT_DEBUG_HAS
bool "Hearing Access Service debug" bool "Hearing Access Service debug"
help help

View file

@ -34,11 +34,87 @@ static ssize_t read_features(struct bt_conn *conn, const struct bt_gatt_attr *at
sizeof(has.features)); sizeof(has.features));
} }
#if CONFIG_BT_HAS_PRESET_COUNT > 0
static uint8_t handle_control_point_op(struct bt_conn *conn, struct net_buf_simple *buf)
{
const struct bt_has_cp_hdr *hdr;
hdr = net_buf_simple_pull_mem(buf, sizeof(*hdr));
BT_DBG("conn %p opcode %s (0x%02x)", (void *)conn, bt_has_op_str(hdr->opcode),
hdr->opcode);
/* TODO: handle request here */
return BT_HAS_ERR_INVALID_OPCODE;
}
static ssize_t write_control_point(struct bt_conn *conn, const struct bt_gatt_attr *attr,
const void *data, uint16_t len, uint16_t offset, uint8_t flags)
{
struct net_buf_simple buf;
uint8_t err;
BT_DBG("conn %p attr %p data %p len %d offset %d flags 0x%02x", (void *)conn, attr, data,
len, offset, flags);
if (offset > 0) {
return BT_GATT_ERR(BT_ATT_ERR_INVALID_OFFSET);
}
if (len == 0) {
return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN);
}
net_buf_simple_init_with_data(&buf, (void *)data, len);
err = handle_control_point_op(conn, &buf);
if (err) {
BT_WARN("err 0x%02x", err);
return BT_GATT_ERR(err);
}
return len;
}
static ssize_t read_active_preset_index(struct bt_conn *conn, const struct bt_gatt_attr *attr,
void *buf, uint16_t len, uint16_t offset)
{
BT_DBG("conn %p attr %p offset %d", (void *)conn, attr, offset);
if (offset > sizeof(has.active_index)) {
return BT_GATT_ERR(BT_ATT_ERR_INVALID_OFFSET);
}
return bt_gatt_attr_read(conn, attr, buf, len, offset, &has.active_index,
sizeof(has.active_index));
}
static void ccc_cfg_changed(const struct bt_gatt_attr *attr, uint16_t value)
{
BT_DBG("attr %p value 0x%04x", attr, value);
}
#endif /* CONFIG_BT_HAS_PRESET_COUNT > 0 */
/* Hearing Access Service GATT Attributes */ /* Hearing Access Service GATT Attributes */
BT_GATT_SERVICE_DEFINE(has_svc, BT_GATT_SERVICE_DEFINE(has_svc,
BT_GATT_PRIMARY_SERVICE(BT_UUID_HAS), BT_GATT_PRIMARY_SERVICE(BT_UUID_HAS),
BT_GATT_CHARACTERISTIC(BT_UUID_HAS_HEARING_AID_FEATURES, BT_GATT_CHRC_READ, BT_GATT_CHARACTERISTIC(BT_UUID_HAS_HEARING_AID_FEATURES, BT_GATT_CHRC_READ,
BT_GATT_PERM_READ_ENCRYPT, read_features, NULL, NULL), BT_GATT_PERM_READ_ENCRYPT, read_features, NULL, NULL),
#if CONFIG_BT_HAS_PRESET_COUNT > 0
BT_GATT_CHARACTERISTIC(BT_UUID_HAS_PRESET_CONTROL_POINT,
#if defined(CONFIG_BT_EATT)
BT_GATT_CHRC_WRITE | BT_GATT_CHRC_INDICATE | BT_GATT_CHRC_NOTIFY,
#else
BT_GATT_CHRC_WRITE | BT_GATT_CHRC_INDICATE,
#endif /* CONFIG_BT_EATT */
BT_GATT_PERM_WRITE_ENCRYPT, NULL, write_control_point, NULL),
BT_GATT_CCC(ccc_cfg_changed, BT_GATT_PERM_READ | BT_GATT_PERM_WRITE_ENCRYPT),
BT_GATT_CHARACTERISTIC(BT_UUID_HAS_ACTIVE_PRESET_INDEX,
BT_GATT_CHRC_READ | BT_GATT_CHRC_NOTIFY, BT_GATT_PERM_READ_ENCRYPT,
read_active_preset_index, NULL, NULL),
BT_GATT_CCC(ccc_cfg_changed, BT_GATT_PERM_READ | BT_GATT_PERM_WRITE_ENCRYPT),
#endif /* CONFIG_BT_HAS_PRESET_COUNT > 0 */
); );
static int has_init(const struct device *dev) static int has_init(const struct device *dev)
@ -48,6 +124,20 @@ static int has_init(const struct device *dev)
/* Initialize the supported features characteristic value */ /* Initialize the supported features characteristic value */
has.features = CONFIG_BT_HAS_HEARING_AID_TYPE & BT_HAS_FEAT_HEARING_AID_TYPE_MASK; has.features = CONFIG_BT_HAS_HEARING_AID_TYPE & BT_HAS_FEAT_HEARING_AID_TYPE_MASK;
if (IS_ENABLED(CONFIG_BT_HAS_HEARING_AID_BINAURAL)) {
if (IS_ENABLED(CONFIG_BT_HAS_PRESET_SYNC_SUPPORT)) {
has.features |= BT_HAS_FEAT_PRESET_SYNC_SUPP;
}
if (!IS_ENABLED(CONFIG_BT_HAS_IDENTICAL_PRESET_RECORDS)) {
has.features |= BT_HAS_FEAT_INDEPENDENT_PRESETS;
}
}
if (IS_ENABLED(CONFIG_BT_HAS_PRESET_NAME_DYNAMIC)) {
has.features |= BT_HAS_FEAT_WRITABLE_PRESETS_SUPP;
}
if (IS_ENABLED(CONFIG_BT_HAS_HEARING_AID_BANDED)) { if (IS_ENABLED(CONFIG_BT_HAS_HEARING_AID_BANDED)) {
/* HAP_d1.0r00; 3.7 BAP Unicast Server role requirements /* HAP_d1.0r00; 3.7 BAP Unicast Server role requirements
* A Banded Hearing Aid in the HA role shall set the Front Left and the Front * A Banded Hearing Aid in the HA role shall set the Front Left and the Front

View file

@ -8,9 +8,121 @@
* SPDX-License-Identifier: Apache-2.0 * SPDX-License-Identifier: Apache-2.0
*/ */
#define BT_HAS_FEAT_HEARING_AID_TYPE_MASK 0x03 /* Control Point opcodes */
#define BT_HAS_OP_READ_PRESET_REQ 0x01
#define BT_HAS_OP_READ_PRESET_RSP 0x02
#define BT_HAS_OP_PRESET_CHANGED 0x03
#define BT_HAS_OP_WRITE_PRESET_NAME 0x04
#define BT_HAS_OP_SET_ACTIVE_PRESET 0x05
#define BT_HAS_OP_SET_NEXT_PRESET 0x06
#define BT_HAS_OP_SET_PREV_PRESET 0x07
#define BT_HAS_OP_SET_ACTIVE_PRESET_SYNC 0x08
#define BT_HAS_OP_SET_NEXT_PRESET_SYNC 0x09
#define BT_HAS_OP_SET_PREV_PRESET_SYNC 0x0a
/* Application error codes */
#define BT_HAS_ERR_INVALID_OPCODE 0x80
#define BT_HAS_ERR_WRITE_NAME_NOT_ALLOWED 0x81
#define BT_HAS_ERR_PRESET_SYNC_NOT_SUPP 0x82
#define BT_HAS_ERR_OPERATION_NOT_POSSIBLE 0x83
#define BT_HAS_ERR_INVALID_PARAM_LEN 0x84
/* Hearing Aid Feature bits */
#define BT_HAS_FEAT_HEARING_AID_TYPE_LSB BIT(0)
#define BT_HAS_FEAT_HEARING_AID_TYPE_MSB BIT(1)
#define BT_HAS_FEAT_PRESET_SYNC_SUPP BIT(2)
#define BT_HAS_FEAT_INDEPENDENT_PRESETS BIT(3)
#define BT_HAS_FEAT_DYNAMIC_PRESETS BIT(4)
#define BT_HAS_FEAT_WRITABLE_PRESETS_SUPP BIT(5)
#define BT_HAS_FEAT_HEARING_AID_TYPE_MASK (BT_HAS_FEAT_HEARING_AID_TYPE_LSB | \
BT_HAS_FEAT_HEARING_AID_TYPE_MSB)
/* Preset Changed Change ID values */
#define BT_HAS_CHANGE_ID_GENERIC_UPDATE 0x00
#define BT_HAS_CHANGE_ID_PRESET_DELETED 0x01
#define BT_HAS_CHANGE_ID_PRESET_AVAILABLE 0x02
#define BT_HAS_CHANGE_ID_PRESET_UNAVAILABLE 0x03
struct bt_has { struct bt_has {
/** Hearing Aid Features value */ /** Hearing Aid Features value */
uint8_t features; uint8_t features;
/** Active preset index value */
uint8_t active_index;
}; };
struct bt_has_cp_hdr {
uint8_t opcode;
uint8_t data[0];
} __packed;
struct bt_has_cp_read_presets_req {
uint8_t start_index;
uint8_t num_presets;
} __packed;
struct bt_has_cp_read_preset_rsp {
uint8_t is_last;
uint8_t index;
uint8_t properties;
uint8_t name[0];
} __packed;
struct bt_has_cp_preset_changed {
uint8_t change_id;
uint8_t is_last;
} __packed;
struct bt_has_cp_write_preset_name {
uint8_t index;
uint8_t name[0];
} __packed;
struct bt_has_cp_set_active_preset {
uint8_t index;
} __packed;
static inline const char *bt_has_op_str(uint8_t op)
{
switch (op) {
case BT_HAS_OP_READ_PRESET_REQ:
return "Read preset request";
case BT_HAS_OP_READ_PRESET_RSP:
return "Read preset response";
case BT_HAS_OP_PRESET_CHANGED:
return "Preset changed";
case BT_HAS_OP_WRITE_PRESET_NAME:
return "Write preset name";
case BT_HAS_OP_SET_ACTIVE_PRESET:
return "Set active preset";
case BT_HAS_OP_SET_NEXT_PRESET:
return "Set next preset";
case BT_HAS_OP_SET_PREV_PRESET:
return "Set previous preset";
case BT_HAS_OP_SET_ACTIVE_PRESET_SYNC:
return "Set active preset (sync)";
case BT_HAS_OP_SET_NEXT_PRESET_SYNC:
return "Set next preset (sync)";
case BT_HAS_OP_SET_PREV_PRESET_SYNC:
return "Set previous preset (sync)";
default:
return "Unknown";
}
}
static inline const char *bt_has_change_id_str(uint8_t change_id)
{
switch (change_id) {
case BT_HAS_CHANGE_ID_GENERIC_UPDATE:
return "Generic update";
case BT_HAS_CHANGE_ID_PRESET_DELETED:
return "Preset deleted";
case BT_HAS_CHANGE_ID_PRESET_AVAILABLE:
return "Preset available";
case BT_HAS_CHANGE_ID_PRESET_UNAVAILABLE:
return "Preset unavailable";
default:
return "Unknown changeId";
}
}