/* btp_hap.c - Bluetooth HAP Tester */ /* * Copyright (c) 2023 Codecoup * * SPDX-License-Identifier: Apache-2.0 */ #include #include #include #include #include #include #include "../bluetooth/audio/has_internal.h" #include "btp/btp.h" #include 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; }