diff --git a/tests/bluetooth/tester/CMakeLists.txt b/tests/bluetooth/tester/CMakeLists.txt index 2a407f2d20a..e44159223fc 100644 --- a/tests/bluetooth/tester/CMakeLists.txt +++ b/tests/bluetooth/tester/CMakeLists.txt @@ -35,3 +35,7 @@ endif() if(CONFIG_BT_BAP_UNICAST) target_sources(app PRIVATE src/btp_bap.c) endif() + +if(CONFIG_BT_HAS) + target_sources(app PRIVATE src/btp_has.c) +endif() diff --git a/tests/bluetooth/tester/overlay-le-audio.conf b/tests/bluetooth/tester/overlay-le-audio.conf index e4a79bc9b2c..1645f10c8db 100644 --- a/tests/bluetooth/tester/overlay-le-audio.conf +++ b/tests/bluetooth/tester/overlay-le-audio.conf @@ -45,3 +45,8 @@ CONFIG_BT_VCP_VOL_REND_LOG_LEVEL_DBG=y # IAS CONFIG_BT_IAS=y CONFIG_BT_IAS_CLIENT=y + +# HAS +CONFIG_BT_HAS=y +CONFIG_BT_HAS_PRESET_COUNT=5 +CONFIG_BT_HAS_PRESET_NAME_DYNAMIC=y diff --git a/tests/bluetooth/tester/src/btp/btp.h b/tests/bluetooth/tester/src/btp/btp.h index c204e6c1d55..3edbf6b036f 100644 --- a/tests/bluetooth/tester/src/btp/btp.h +++ b/tests/bluetooth/tester/src/btp/btp.h @@ -23,6 +23,7 @@ #include "btp_pacs.h" #include "btp_ascs.h" #include "btp_bap.h" +#include "btp_has.h" #define BTP_MTU 1024 #define BTP_DATA_MAX_SIZE (BTP_MTU - sizeof(struct btp_hdr)) @@ -45,7 +46,9 @@ #define BTP_SERVICE_ID_PACS 12 #define BTP_SERVICE_ID_ASCS 13 #define BTP_SERVICE_ID_BAP 14 -#define BTP_SERVICE_ID_MAX BTP_SERVICE_ID_BAP +#define BTP_SERVICE_ID_HAS 15 + +#define BTP_SERVICE_ID_MAX BTP_SERVICE_ID_HAS #define BTP_STATUS_SUCCESS 0x00 #define BTP_STATUS_FAILED 0x01 @@ -65,7 +68,7 @@ struct btp_hdr { uint8_t data[]; } __packed; -#define BTP_STATUS 0x00 +#define BTP_STATUS 0x00 struct btp_status { uint8_t code; } __packed; diff --git a/tests/bluetooth/tester/src/btp/btp_has.h b/tests/bluetooth/tester/src/btp/btp_has.h new file mode 100644 index 00000000000..fc87a3f3239 --- /dev/null +++ b/tests/bluetooth/tester/src/btp/btp_has.h @@ -0,0 +1,61 @@ +/* btp_has.h - Bluetooth tester headers */ + +/* + * Copyright (c) 2023 Oticon + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/* HAS commands */ +#define BTP_HAS_READ_SUPPORTED_COMMANDS 0x01 +struct btp_has_read_supported_commands_rp { + uint8_t data[0]; +} __packed; + +#define BTP_HAS_SET_ACTIVE_INDEX 0x02 +struct btp_has_set_active_index_cmd { + bt_addr_le_t address; + uint8_t index; +} __packed; + +#define BTP_HAS_SET_PRESET_NAME 0x03 +struct btp_has_set_preset_name_cmd { + bt_addr_le_t address; + uint8_t index; + uint8_t length; + char name[0]; +} __packed; + +#define BTP_HAS_REMOVE_PRESET 0x04 +struct btp_has_remove_preset_cmd { + bt_addr_le_t address; + uint8_t index; +} __packed; + +#define BTP_HAS_ADD_PRESET 0x05 +struct btp_has_add_preset_cmd { + bt_addr_le_t address; + uint8_t index; + uint8_t props; + uint8_t length; + char name[0]; +} __packed; + +#define BTP_HAS_SET_PROPERTIES 0x06 +struct btp_has_set_properties_cmd { + bt_addr_le_t address; + uint8_t index; + uint8_t props; +} __packed; + +/* HAS events */ +#define BTP_HAS_EV_OPERATION_COMPLETED 0x80 +struct btp_has_operation_completed_ev { + bt_addr_le_t address; + uint8_t index; + uint8_t opcode; + uint8_t status; + + /* RFU */ + uint8_t flags; +} __packed; diff --git a/tests/bluetooth/tester/src/btp/bttester.h b/tests/bluetooth/tester/src/btp/bttester.h index 7550d036062..a68cb3c5c73 100644 --- a/tests/bluetooth/tester/src/btp/bttester.h +++ b/tests/bluetooth/tester/src/btp/bttester.h @@ -86,3 +86,6 @@ uint8_t tester_unregister_ascs(void); uint8_t tester_init_bap(void); uint8_t tester_unregister_bap(void); + +uint8_t tester_init_has(void); +uint8_t tester_unregister_has(void); diff --git a/tests/bluetooth/tester/src/btp_core.c b/tests/bluetooth/tester/src/btp_core.c index 5076d7c4a1b..b0b9a366a93 100644 --- a/tests/bluetooth/tester/src/btp_core.c +++ b/tests/bluetooth/tester/src/btp_core.c @@ -74,6 +74,9 @@ static uint8_t supported_services(const void *cmd, uint16_t cmd_len, #if defined(CONFIG_BT_VOCS) || defined(CONFIG_BT_VOCS_CLIENT) tester_set_bit(rp->data, BTP_SERVICE_ID_VOCS); #endif /* CONFIG_BT_VOCS */ +#if defined(CONFIG_BT_HAS) || defined(CONFIG_BT_HAS_CLIENT) + tester_set_bit(rp->data, BTP_SERVICE_ID_HAS); +#endif /* CONFIG_BT_HAS */ *rsp_len = sizeof(*rp) + 2; @@ -140,6 +143,11 @@ static uint8_t register_service(const void *cmd, uint16_t cmd_len, status = tester_init_bap(); break; #endif /* CONFIG_BT_BAP_UNICAST_CLIENT or CONFIG_BT_BAP_UNICAST_SERVER */ +#if defined(CONFIG_BT_HAS) + case BTP_SERVICE_ID_HAS: + status = tester_init_has(); + break; +#endif /* CONFIG_BT_HAS */ default: LOG_WRN("unknown id: 0x%02x", cp->id); status = BTP_STATUS_FAILED; @@ -213,6 +221,11 @@ static uint8_t unregister_service(const void *cmd, uint16_t cmd_len, status = tester_unregister_bap(); break; #endif /* CONFIG_BT_BAP_UNICAST_CLIENT or CONFIG_BT_BAP_UNICAST_SERVER */ +#if defined(CONFIG_BT_HAS) + case BTP_SERVICE_ID_HAS: + status = tester_unregister_has(); + break; +#endif /* CONFIG_BT_HAS */ default: LOG_WRN("unknown id: 0x%x", cp->id); status = BTP_STATUS_FAILED; diff --git a/tests/bluetooth/tester/src/btp_has.c b/tests/bluetooth/tester/src/btp_has.c new file mode 100644 index 00000000000..1ea93a593e7 --- /dev/null +++ b/tests/bluetooth/tester/src/btp_has.c @@ -0,0 +1,215 @@ +/* btp_has.c - Bluetooth HAS Tester */ + +/* + * Copyright (c) 2023 Oticon + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include + +#include "btp/btp.h" +#include "zephyr/sys/byteorder.h" +#include "zephyr/arch/common/ffs.h" +#include + +#include +#define LOG_MODULE_NAME bttester_has +LOG_MODULE_REGISTER(LOG_MODULE_NAME, CONFIG_BTTESTER_LOG_LEVEL); + +static uint8_t has_supported_commands(const void *cmd, uint16_t cmd_len, + void *rsp, uint16_t *rsp_len) +{ + struct btp_has_read_supported_commands_rp *rp = rsp; + + /* octet 0 */ + tester_set_bit(rp->data, BTP_HAS_READ_SUPPORTED_COMMANDS); + + *rsp_len = sizeof(*rp) + 1; + + return BTP_STATUS_SUCCESS; +} + +static uint8_t has_set_active_index(const void *cmd, uint16_t cmd_len, + void *rsp, uint16_t *rsp_len) +{ + const struct btp_has_set_active_index_cmd *cp = cmd; + int err = bt_has_preset_active_set(cp->index); + + return (err) ? BTP_STATUS_FAILED : BTP_STATUS_SUCCESS; +} + +static uint16_t has_presets; +static char temp_name[BT_HAS_PRESET_NAME_MAX + 1]; + +static uint8_t has_set_preset_name(const void *cmd, uint16_t cmd_len, + void *rsp, uint16_t *rsp_len) +{ + const struct btp_has_set_preset_name_cmd *cp = cmd; + const uint16_t fixed_size = sizeof(*cp); + int err = -1; + + if (cmd_len >= fixed_size && cmd_len >= (fixed_size + cp->length)) { + int name_len = MIN(cp->length, BT_HAS_PRESET_NAME_MAX); + + memcpy(temp_name, cp->name, name_len); + temp_name[name_len] = '\0'; + err = bt_has_preset_name_change(cp->index, temp_name); + } + return (err) ? BTP_STATUS_FAILED : BTP_STATUS_SUCCESS; +} + +static uint8_t has_remove_preset(const void *cmd, uint16_t cmd_len, + void *rsp, uint16_t *rsp_len) +{ + const struct btp_has_remove_preset_cmd *cp = cmd; + int err = 0; + + if (cp->index == BT_HAS_PRESET_INDEX_NONE) { + while (has_presets) { + uint8_t index = find_lsb_set(has_presets); + + err = bt_has_preset_unregister(index); + if (err) { + break; + } + has_presets &= ~(1 << (index - 1)); + } + } else { + err = bt_has_preset_unregister(cp->index); + if (!err) { + has_presets &= ~(1 << (cp->index - 1)); + } + } + return (err) ? BTP_STATUS_FAILED : BTP_STATUS_SUCCESS; +} + +static int has_preset_selected(unsigned char index, bool sync) +{ + return BTP_STATUS_SUCCESS; +} + +static const struct bt_has_preset_ops has_preset_ops = { + has_preset_selected, NULL +}; + +static uint8_t has_add_preset(const void *cmd, uint16_t cmd_len, + void *rsp, uint16_t *rsp_len) +{ + const struct btp_has_add_preset_cmd *cp = cmd; + const uint16_t fixed_size = sizeof(*cp); + int err = -1; + + if (cmd_len >= fixed_size && cmd_len >= (fixed_size + cp->length)) { + int name_len = MIN(cp->length, BT_HAS_PRESET_NAME_MAX); + + memcpy(temp_name, cp->name, name_len); + temp_name[name_len] = '\0'; + struct bt_has_preset_register_param preset_params = { + cp->index, cp->props, temp_name, &has_preset_ops + }; + err = bt_has_preset_register(&preset_params); + if (!err) { + has_presets |= 1 << (cp->index - 1); + } + } + return (err) ? BTP_STATUS_FAILED : BTP_STATUS_SUCCESS; +} + +static uint8_t has_set_properties(const void *cmd, uint16_t cmd_len, + void *rsp, uint16_t *rsp_len) +{ + const struct btp_has_set_properties_cmd *cp = cmd; + int err = (cp->props & BT_HAS_PROP_AVAILABLE) ? + bt_has_preset_available(cp->index) : + bt_has_preset_unavailable(cp->index); + + return (err) ? BTP_STATUS_FAILED : BTP_STATUS_SUCCESS; +} + +static const struct btp_handler has_handlers[] = { + { + .opcode = BTP_HAS_READ_SUPPORTED_COMMANDS, + .index = BTP_INDEX_NONE, + .expect_len = 0, + .func = has_supported_commands + }, + { + .opcode = BTP_HAS_SET_ACTIVE_INDEX, + .expect_len = sizeof(struct btp_has_set_active_index_cmd), + .func = has_set_active_index + }, + { + .opcode = BTP_HAS_SET_PRESET_NAME, + .expect_len = BTP_HANDLER_LENGTH_VARIABLE, + .func = has_set_preset_name + }, + { + .opcode = BTP_HAS_REMOVE_PRESET, + .expect_len = sizeof(struct btp_has_remove_preset_cmd), + .func = has_remove_preset + }, + { + .opcode = BTP_HAS_ADD_PRESET, + .expect_len = BTP_HANDLER_LENGTH_VARIABLE, + .func = has_add_preset + }, + { + .opcode = BTP_HAS_SET_PROPERTIES, + .expect_len = sizeof(struct btp_has_set_properties_cmd), + .func = has_set_properties + } +}; + +static const char *preset_name(uint8_t index) +{ + switch (index) { + case 0: return "PRESET_0"; + case 1: return "PRESET_1"; + case 2: return "PRESET_2"; + case 3: return "PRESET_3"; + case 4: return "PRESET_4"; + case 5: return "PRESET_5"; + case 6: return "PRESET_6"; + case 7: return "PRESET_7"; + default: return "PRESET_?"; + } +} + +#define PRESETS_CONFIGURED 3 +#define LAST_PRESET MIN(PRESETS_CONFIGURED, CONFIG_BT_HAS_PRESET_COUNT) + +uint8_t tester_init_has(void) +{ + tester_register_command_handlers(BTP_SERVICE_ID_HAS, has_handlers, + ARRAY_SIZE(has_handlers)); + + struct bt_has_register_param params = { + BT_HAS_HEARING_AID_TYPE_BINAURAL, false, true + }; + int err = bt_has_register(¶ms); + + if (!err) { + for (uint8_t index = 1; index <= LAST_PRESET; index++) { + enum bt_has_properties properties = (index < PRESETS_CONFIGURED) ? + BT_HAS_PROP_WRITABLE | BT_HAS_PROP_AVAILABLE : + BT_HAS_PROP_WRITABLE; + struct bt_has_preset_register_param preset_params = { + index, properties, preset_name(index), &has_preset_ops + }; + + err = bt_has_preset_register(&preset_params); + if (!err) { + has_presets |= 1 << (index - 1); + } else { + break; + } + } + } + + return (err) ? BTP_STATUS_FAILED : BTP_STATUS_SUCCESS; +} + +uint8_t tester_unregister_has(void) +{ + return BTP_STATUS_SUCCESS; +}