tests: Bluetooth: tester: Add initial support for HAP
This adds initial support for Hearing Aid Profile BTP service commands. Signed-off-by: Mariusz Skamra <mariusz.skamra@codecoup.pl>
This commit is contained in:
parent
b79a3415a9
commit
4a77bdb4e8
@ -63,3 +63,7 @@ endif()
|
||||
if(CONFIG_BT_MCC OR CONFIG_BT_MCS)
|
||||
target_sources(app PRIVATE src/btp_mcp.c)
|
||||
endif()
|
||||
|
||||
if(CONFIG_BT_HAS)
|
||||
target_sources(app PRIVATE src/btp_hap.c)
|
||||
endif()
|
||||
|
||||
@ -90,6 +90,8 @@ CONFIG_BT_HAS=y
|
||||
CONFIG_BT_HAS_PRESET_COUNT=6
|
||||
CONFIG_BT_HAS_PRESET_NAME_DYNAMIC=y
|
||||
|
||||
CONFIG_BT_HAS_CLIENT=y
|
||||
|
||||
# CSIS
|
||||
CONFIG_BT_CSIP_SET_MEMBER=y
|
||||
|
||||
|
||||
@ -32,6 +32,7 @@
|
||||
#include "btp_cas.h"
|
||||
#include "btp_mcp.h"
|
||||
#include "btp_mcs.h"
|
||||
#include "btp_hap.h"
|
||||
|
||||
#define BTP_MTU 1024
|
||||
#define BTP_DATA_MAX_SIZE (BTP_MTU - sizeof(struct btp_hdr))
|
||||
@ -63,8 +64,9 @@
|
||||
#define BTP_SERVICE_ID_CAS 21
|
||||
#define BTP_SERVICE_ID_MCP 22
|
||||
#define BTP_SERVICE_ID_GMCS 23
|
||||
#define BTP_SERVICE_ID_HAP 24
|
||||
|
||||
#define BTP_SERVICE_ID_MAX BTP_SERVICE_ID_GMCS
|
||||
#define BTP_SERVICE_ID_MAX BTP_SERVICE_ID_HAP
|
||||
|
||||
#define BTP_STATUS_SUCCESS 0x00
|
||||
#define BTP_STATUS_FAILED 0x01
|
||||
|
||||
61
tests/bluetooth/tester/src/btp/btp_hap.h
Normal file
61
tests/bluetooth/tester/src/btp/btp_hap.h
Normal file
@ -0,0 +1,61 @@
|
||||
/* btp_hap.h - Bluetooth tester headers */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2023 Codecoup
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
#include <zephyr/types.h>
|
||||
|
||||
/* HAP commands */
|
||||
#define BTP_HAP_READ_SUPPORTED_COMMANDS 0x01
|
||||
struct btp_hap_read_supported_commands_rp {
|
||||
uint8_t data[0];
|
||||
} __packed;
|
||||
|
||||
#define BTP_HAP_HA_OPT_PRESETS_SYNC 0x01
|
||||
#define BTP_HAP_HA_OPT_PRESETS_INDEPENDENT 0x02
|
||||
#define BTP_HAP_HA_OPT_PRESETS_DYNAMIC 0x04
|
||||
#define BTP_HAP_HA_OPT_PRESETS_WRITABLE 0x08
|
||||
|
||||
#define BTP_HAP_HA_INIT 0x02
|
||||
struct btp_hap_ha_init_cmd {
|
||||
uint8_t type;
|
||||
uint16_t opts;
|
||||
} __packed;
|
||||
|
||||
#define BTP_HAP_HARC_INIT 0x03
|
||||
#define BTP_HAP_HAUC_INIT 0x04
|
||||
#define BTP_HAP_IAC_INIT 0x05
|
||||
|
||||
#define BTP_HAP_IAC_DISCOVER 0x06
|
||||
struct btp_hap_iac_discover_cmd {
|
||||
bt_addr_le_t address;
|
||||
} __packed;
|
||||
|
||||
#define BTP_HAP_IAC_SET_ALERT 0x07
|
||||
struct btp_hap_iac_set_alert_cmd {
|
||||
bt_addr_le_t address;
|
||||
uint8_t alert;
|
||||
} __packed;
|
||||
|
||||
#define BTP_HAP_HAUC_DISCOVER 0x08
|
||||
struct btp_hap_hauc_discover_cmd {
|
||||
bt_addr_le_t address;
|
||||
} __packed;
|
||||
|
||||
/* HAP events */
|
||||
#define BT_HAP_EV_IAC_DISCOVERY_COMPLETE 0x80
|
||||
struct btp_hap_iac_discovery_complete_ev {
|
||||
bt_addr_le_t address;
|
||||
uint8_t status;
|
||||
} __packed;
|
||||
|
||||
#define BT_HAP_EV_HAUC_DISCOVERY_COMPLETE 0x81
|
||||
struct btp_hap_hauc_discovery_complete_ev {
|
||||
bt_addr_le_t address;
|
||||
uint8_t status;
|
||||
uint16_t has_hearing_aid_features_handle;
|
||||
uint16_t has_control_point_handle;
|
||||
uint16_t has_active_preset_index_handle;
|
||||
} __packed;
|
||||
@ -120,3 +120,6 @@ uint8_t tester_unregister_mcp(void);
|
||||
|
||||
uint8_t tester_init_mcs(void);
|
||||
uint8_t tester_unregister_mcs(void);
|
||||
|
||||
uint8_t tester_init_hap(void);
|
||||
uint8_t tester_unregister_hap(void);
|
||||
|
||||
@ -98,6 +98,9 @@ static uint8_t supported_services(const void *cmd, uint16_t cmd_len,
|
||||
#if defined(CONFIG_BT_MCS)
|
||||
tester_set_bit(rp->data, BTP_SERVICE_ID_GMCS);
|
||||
#endif /* CONFIG_BT_MCS */
|
||||
#if defined(CONFIG_BT_HAS)
|
||||
tester_set_bit(rp->data, BTP_SERVICE_ID_HAP);
|
||||
#endif /* CONFIG_BT_HAS */
|
||||
|
||||
*rsp_len = sizeof(*rp) + 2;
|
||||
|
||||
@ -211,6 +214,11 @@ static uint8_t register_service(const void *cmd, uint16_t cmd_len,
|
||||
status = tester_init_mcs();
|
||||
break;
|
||||
#endif /* CONFIG_BT_MCS */
|
||||
#if defined(CONFIG_BT_HAS)
|
||||
case BTP_SERVICE_ID_HAP:
|
||||
status = tester_init_hap();
|
||||
break;
|
||||
#endif /* CONFIG_BT_HAS */
|
||||
default:
|
||||
LOG_WRN("unknown id: 0x%02x", cp->id);
|
||||
status = BTP_STATUS_FAILED;
|
||||
@ -328,6 +336,11 @@ static uint8_t unregister_service(const void *cmd, uint16_t cmd_len,
|
||||
status = tester_unregister_mcs();
|
||||
break;
|
||||
#endif /* CONFIG_BT_MCS */
|
||||
#if defined(CONFIG_BT_HAS)
|
||||
case BTP_SERVICE_ID_HAP:
|
||||
status = tester_unregister_hap();
|
||||
break;
|
||||
#endif /* CONFIG_BT_HAS */
|
||||
default:
|
||||
LOG_WRN("unknown id: 0x%x", cp->id);
|
||||
status = BTP_STATUS_FAILED;
|
||||
|
||||
277
tests/bluetooth/tester/src/btp_hap.c
Normal file
277
tests/bluetooth/tester/src/btp_hap.c
Normal file
@ -0,0 +1,277 @@
|
||||
/* btp_hap.c - Bluetooth HAP Tester */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2023 Codecoup
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
#include <zephyr/sys/byteorder.h>
|
||||
#include <zephyr/bluetooth/audio/audio.h>
|
||||
#include <zephyr/bluetooth/audio/bap.h>
|
||||
#include <zephyr/bluetooth/audio/has.h>
|
||||
#include <zephyr/bluetooth/audio/pacs.h>
|
||||
#include <zephyr/bluetooth/services/ias.h>
|
||||
|
||||
#include "../bluetooth/audio/has_internal.h"
|
||||
#include "btp/btp.h"
|
||||
|
||||
#include <zephyr/logging/log.h>
|
||||
LOG_MODULE_REGISTER(bttester_hap, CONFIG_BTTESTER_LOG_LEVEL);
|
||||
|
||||
static uint8_t read_supported_commands(const void *cmd, uint16_t cmd_len, void *rsp,
|
||||
uint16_t *rsp_len)
|
||||
{
|
||||
struct btp_hap_read_supported_commands_rp *rp = rsp;
|
||||
|
||||
tester_set_bit(rp->data, BTP_HAP_READ_SUPPORTED_COMMANDS);
|
||||
tester_set_bit(rp->data, BTP_HAP_HA_INIT);
|
||||
tester_set_bit(rp->data, BTP_HAP_HAUC_INIT);
|
||||
tester_set_bit(rp->data, BTP_HAP_IAC_INIT);
|
||||
tester_set_bit(rp->data, BTP_HAP_IAC_DISCOVER);
|
||||
tester_set_bit(rp->data, BTP_HAP_IAC_SET_ALERT);
|
||||
tester_set_bit(rp->data, BTP_HAP_HAUC_DISCOVER);
|
||||
|
||||
*rsp_len = sizeof(*rp) + 1;
|
||||
|
||||
return BTP_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
static uint8_t ha_init(const void *cmd, uint16_t cmd_len, void *rsp, uint16_t *rsp_len)
|
||||
{
|
||||
const struct btp_hap_ha_init_cmd *cp = cmd;
|
||||
struct bt_has_features_param params;
|
||||
const uint16_t opts = sys_le16_to_cpu(cp->opts);
|
||||
const bool presets_sync = (opts & BTP_HAP_HA_OPT_PRESETS_SYNC) > 0;
|
||||
const bool presets_independent = (opts & BTP_HAP_HA_OPT_PRESETS_INDEPENDENT) > 0;
|
||||
const bool presets_writable = (opts & BTP_HAP_HA_OPT_PRESETS_WRITABLE) > 0;
|
||||
const bool presets_dynamic = (opts & BTP_HAP_HA_OPT_PRESETS_DYNAMIC) > 0;
|
||||
int err;
|
||||
|
||||
if (!IS_ENABLED(CONFIG_BT_HAS_PRESET_SUPPORT) &&
|
||||
(presets_sync || presets_independent || presets_writable || presets_dynamic)) {
|
||||
return BTP_STATUS_VAL(-ENOTSUP);
|
||||
}
|
||||
|
||||
/* Only dynamic presets are supported */
|
||||
if (!presets_dynamic) {
|
||||
return BTP_STATUS_VAL(-ENOTSUP);
|
||||
}
|
||||
|
||||
/* Preset name writable support mismatch */
|
||||
if (presets_writable != IS_ENABLED(CONFIG_BT_HAS_PRESET_NAME_DYNAMIC)) {
|
||||
return BTP_STATUS_VAL(-ENOTSUP);
|
||||
}
|
||||
|
||||
params.type = cp->type;
|
||||
params.preset_sync_support = presets_sync;
|
||||
params.independent_presets = presets_independent;
|
||||
|
||||
if (cp->type == BT_HAS_HEARING_AID_TYPE_BANDED) {
|
||||
err = bt_pacs_set_location(BT_AUDIO_DIR_SINK, BT_AUDIO_LOCATION_FRONT_LEFT |
|
||||
BT_AUDIO_LOCATION_FRONT_RIGHT);
|
||||
} else {
|
||||
err = bt_pacs_set_location(BT_AUDIO_DIR_SINK, BT_AUDIO_LOCATION_FRONT_LEFT);
|
||||
}
|
||||
|
||||
if (err != 0) {
|
||||
return BTP_STATUS_VAL(err);
|
||||
}
|
||||
|
||||
err = bt_has_register(¶ms);
|
||||
if (err != 0) {
|
||||
return BTP_STATUS_VAL(err);
|
||||
}
|
||||
|
||||
return BTP_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
static void has_client_discover_cb(struct bt_conn *conn, int err, struct bt_has *has,
|
||||
enum bt_has_hearing_aid_type type, enum bt_has_capabilities caps)
|
||||
{
|
||||
struct btp_hap_hauc_discovery_complete_ev ev = { 0 };
|
||||
|
||||
LOG_DBG("conn %p err %d", (void *)conn, err);
|
||||
|
||||
bt_addr_le_copy(&ev.address, bt_conn_get_dst(conn));
|
||||
ev.status = BTP_STATUS_VAL(err);
|
||||
|
||||
if (err != 0 && err != BT_ATT_ERR_ATTRIBUTE_NOT_FOUND) {
|
||||
LOG_DBG("Client discovery failed: %d", err);
|
||||
} else {
|
||||
struct bt_has_client *inst = CONTAINER_OF(has, struct bt_has_client, has);
|
||||
|
||||
ev.has_hearing_aid_features_handle = inst->features_subscription.value_handle;
|
||||
ev.has_control_point_handle = inst->control_point_subscription.value_handle;
|
||||
ev.has_active_preset_index_handle = inst->active_index_subscription.value_handle;
|
||||
}
|
||||
|
||||
tester_event(BTP_SERVICE_ID_HAP, BT_HAP_EV_HAUC_DISCOVERY_COMPLETE, &ev, sizeof(ev));
|
||||
}
|
||||
|
||||
static void has_client_preset_switch_cb(struct bt_has *has, int err, uint8_t index)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
static const struct bt_has_client_cb has_client_cb = {
|
||||
.discover = has_client_discover_cb,
|
||||
.preset_switch = has_client_preset_switch_cb,
|
||||
};
|
||||
|
||||
static uint8_t hauc_init(const void *cmd, uint16_t cmd_len, void *rsp, uint16_t *rsp_len)
|
||||
{
|
||||
int err;
|
||||
|
||||
err = bt_has_client_cb_register(&has_client_cb);
|
||||
if (err != 0) {
|
||||
LOG_DBG("Failed to register client callbacks: %d", err);
|
||||
return BTP_STATUS_FAILED;
|
||||
}
|
||||
|
||||
return BTP_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
static void ias_client_discover_cb(struct bt_conn *conn, int err)
|
||||
{
|
||||
struct btp_hap_iac_discovery_complete_ev ev;
|
||||
struct bt_conn_info info;
|
||||
|
||||
bt_conn_get_info(conn, &info);
|
||||
|
||||
bt_addr_le_copy(&ev.address, info.le.dst);
|
||||
if (err < 0) {
|
||||
ev.status = BT_ATT_ERR_UNLIKELY;
|
||||
} else {
|
||||
ev.status = (uint8_t)err;
|
||||
}
|
||||
|
||||
tester_event(BTP_SERVICE_ID_HAP, BT_HAP_EV_IAC_DISCOVERY_COMPLETE, &ev, sizeof(ev));
|
||||
}
|
||||
|
||||
static const struct bt_ias_client_cb ias_client_cb = {
|
||||
.discover = ias_client_discover_cb,
|
||||
};
|
||||
|
||||
static uint8_t iac_init(const void *cmd, uint16_t cmd_len, void *rsp, uint16_t *rsp_len)
|
||||
{
|
||||
int err;
|
||||
|
||||
err = bt_ias_client_cb_register(&ias_client_cb);
|
||||
if (err != 0) {
|
||||
return BTP_STATUS_VAL(err);
|
||||
}
|
||||
|
||||
return BTP_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
static uint8_t iac_discover(const void *cmd, uint16_t cmd_len, void *rsp, uint16_t *rsp_len)
|
||||
{
|
||||
const struct btp_hap_iac_discover_cmd *cp = cmd;
|
||||
struct bt_conn *conn;
|
||||
int err;
|
||||
|
||||
conn = bt_conn_lookup_addr_le(BT_ID_DEFAULT, &cp->address);
|
||||
if (!conn) {
|
||||
LOG_ERR("Unknown connection");
|
||||
return BTP_STATUS_FAILED;
|
||||
}
|
||||
|
||||
err = bt_ias_discover(conn);
|
||||
|
||||
bt_conn_unref(conn);
|
||||
|
||||
return BTP_STATUS_VAL(err);
|
||||
}
|
||||
|
||||
static uint8_t iac_set_alert(const void *cmd, uint16_t cmd_len, void *rsp, uint16_t *rsp_len)
|
||||
{
|
||||
const struct btp_hap_iac_set_alert_cmd *cp = cmd;
|
||||
struct bt_conn *conn;
|
||||
int err;
|
||||
|
||||
conn = bt_conn_lookup_addr_le(BT_ID_DEFAULT, &cp->address);
|
||||
if (!conn) {
|
||||
LOG_ERR("Unknown connection");
|
||||
return BTP_STATUS_FAILED;
|
||||
}
|
||||
|
||||
err = bt_ias_client_alert_write(conn, (enum bt_ias_alert_lvl)cp->alert);
|
||||
|
||||
bt_conn_unref(conn);
|
||||
|
||||
return BTP_STATUS_VAL(err);
|
||||
}
|
||||
|
||||
static uint8_t hauc_discover(const void *cmd, uint16_t cmd_len, void *rsp, uint16_t *rsp_len)
|
||||
{
|
||||
const struct btp_hap_hauc_discover_cmd *cp = cmd;
|
||||
struct bt_conn *conn;
|
||||
int err;
|
||||
|
||||
conn = bt_conn_lookup_addr_le(BT_ID_DEFAULT, &cp->address);
|
||||
if (!conn) {
|
||||
LOG_ERR("Unknown connection");
|
||||
return BTP_STATUS_FAILED;
|
||||
}
|
||||
|
||||
err = bt_has_client_discover(conn);
|
||||
if (err != 0) {
|
||||
LOG_DBG("Failed to discover remote HAS: %d", err);
|
||||
}
|
||||
|
||||
bt_conn_unref(conn);
|
||||
|
||||
return BTP_STATUS_VAL(err);
|
||||
}
|
||||
|
||||
static const struct btp_handler hap_handlers[] = {
|
||||
{
|
||||
.opcode = BTP_HAP_READ_SUPPORTED_COMMANDS,
|
||||
.index = BTP_INDEX_NONE,
|
||||
.expect_len = 0,
|
||||
.func = read_supported_commands,
|
||||
},
|
||||
{
|
||||
.opcode = BTP_HAP_HA_INIT,
|
||||
.expect_len = sizeof(struct btp_hap_ha_init_cmd),
|
||||
.func = ha_init,
|
||||
},
|
||||
{
|
||||
.opcode = BTP_HAP_HAUC_INIT,
|
||||
.expect_len = 0,
|
||||
.func = hauc_init,
|
||||
},
|
||||
{
|
||||
.opcode = BTP_HAP_IAC_INIT,
|
||||
.expect_len = 0,
|
||||
.func = iac_init,
|
||||
},
|
||||
{
|
||||
.opcode = BTP_HAP_IAC_DISCOVER,
|
||||
.expect_len = sizeof(struct btp_hap_iac_discover_cmd),
|
||||
.func = iac_discover,
|
||||
},
|
||||
{
|
||||
.opcode = BTP_HAP_IAC_SET_ALERT,
|
||||
.expect_len = sizeof(struct btp_hap_iac_set_alert_cmd),
|
||||
.func = iac_set_alert,
|
||||
},
|
||||
{
|
||||
.opcode = BTP_HAP_HAUC_DISCOVER,
|
||||
.expect_len = sizeof(struct btp_hap_hauc_discover_cmd),
|
||||
.func = hauc_discover,
|
||||
},
|
||||
};
|
||||
|
||||
uint8_t tester_init_hap(void)
|
||||
{
|
||||
tester_register_command_handlers(BTP_SERVICE_ID_HAP, hap_handlers,
|
||||
ARRAY_SIZE(hap_handlers));
|
||||
|
||||
return BTP_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
uint8_t tester_unregister_hap(void)
|
||||
{
|
||||
return BTP_STATUS_SUCCESS;
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user