| /* btp_micp.c - Bluetooth MICP Tester */ |
| |
| /* |
| * Copyright (c) 2023 Codecoup |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #include <stddef.h> |
| #include <errno.h> |
| #include <stdio.h> |
| |
| #include <zephyr/types.h> |
| #include <zephyr/kernel.h> |
| |
| #include <zephyr/bluetooth/bluetooth.h> |
| #include <zephyr/bluetooth/audio/audio.h> |
| #include <zephyr/bluetooth/audio/micp.h> |
| #include <zephyr/bluetooth/audio/aics.h> |
| |
| #include <zephyr/logging/log.h> |
| #include <zephyr/sys/byteorder.h> |
| |
| #include <../../subsys/bluetooth/audio/micp_internal.h> |
| #include <../../subsys/bluetooth/audio/aics_internal.h> |
| |
| #include "bap_endpoint.h" |
| #include "btp/btp.h" |
| |
| #define LOG_MODULE_NAME bttester_micp |
| LOG_MODULE_REGISTER(LOG_MODULE_NAME, CONFIG_BTTESTER_LOG_LEVEL); |
| |
| static struct bt_micp_mic_ctlr *mic_ctlr; |
| static struct bt_micp_mic_dev_register_param mic_dev_register_param; |
| static uint8_t mute_state; |
| |
| #if defined(CONFIG_BT_MICP_MIC_CTLR_AICS) |
| static struct bt_micp_included micp_included; |
| struct chrc_handles { |
| uint16_t mute_handle; |
| uint16_t state_handle; |
| uint16_t gain_handle; |
| uint16_t type_handle; |
| uint16_t status_handle; |
| uint16_t control_handle; |
| uint16_t desc_handle; |
| }; |
| struct chrc_handles micp_handles; |
| extern struct btp_aics_instance aics_client_instance; |
| extern struct bt_aics_cb aics_client_cb; |
| #endif /* CONFIG_BT_MICP_MIC_CTLR_AICS */ |
| |
| /* Microphone Control Profile */ |
| static void btp_send_micp_found_ev(struct bt_conn *conn, uint8_t att_status, |
| const struct chrc_handles *micp_handles) |
| { |
| struct btp_micp_discovered_ev ev; |
| |
| bt_addr_le_copy(&ev.address, bt_conn_get_dst(conn)); |
| |
| ev.att_status = att_status; |
| ev.mute_handle = sys_cpu_to_le16(micp_handles->mute_handle); |
| ev.state_handle = sys_cpu_to_le16(micp_handles->state_handle); |
| ev.gain_handle = sys_cpu_to_le16(micp_handles->gain_handle); |
| ev.type_handle = sys_cpu_to_le16(micp_handles->type_handle); |
| ev.status_handle = sys_cpu_to_le16(micp_handles->status_handle); |
| ev.control_handle = sys_cpu_to_le16(micp_handles->control_handle); |
| ev.desc_handle = sys_cpu_to_le16(micp_handles->desc_handle); |
| |
| tester_event(BTP_SERVICE_ID_MICP, BTP_MICP_DISCOVERED_EV, &ev, sizeof(ev)); |
| } |
| |
| static void btp_send_micp_mute_state_ev(struct bt_conn *conn, uint8_t att_status, uint8_t mute) |
| { |
| struct btp_micp_mute_state_ev ev; |
| |
| bt_addr_le_copy(&ev.address, bt_conn_get_dst(conn)); |
| |
| ev.att_status = att_status; |
| ev.mute = mute; |
| |
| tester_event(BTP_SERVICE_ID_MICP, BTP_MICP_MUTE_STATE_EV, &ev, sizeof(ev)); |
| } |
| |
| static void micp_mic_ctlr_mute_cb(struct bt_micp_mic_ctlr *mic_ctlr, int err, uint8_t mute) |
| { |
| struct bt_conn *conn; |
| |
| mute_state = mute; |
| |
| bt_micp_mic_ctlr_conn_get(mic_ctlr, &conn); |
| btp_send_micp_mute_state_ev(conn, err, mute_state); |
| |
| LOG_DBG("MICP Mute cb (%d)", err); |
| } |
| |
| static void micp_mic_ctlr_mute_written_cb(struct bt_micp_mic_ctlr *mic_ctlr, int err) |
| { |
| struct bt_conn *conn; |
| |
| bt_micp_mic_ctlr_conn_get(mic_ctlr, &conn); |
| btp_send_micp_mute_state_ev(conn, err, mute_state); |
| |
| LOG_DBG("MICP Mute Written cb (%d))", err); |
| } |
| |
| static void micp_mic_ctlr_unmute_written_cb(struct bt_micp_mic_ctlr *mic_ctlr, int err) |
| { |
| struct bt_conn *conn; |
| |
| bt_micp_mic_ctlr_conn_get(mic_ctlr, &conn); |
| btp_send_micp_mute_state_ev(conn, err, mute_state); |
| |
| LOG_DBG("MICP Mute Unwritten cb (%d))", err); |
| } |
| |
| static void micp_mic_ctlr_discover_cb(struct bt_micp_mic_ctlr *mic_ctlr, int err, |
| uint8_t aics_count) |
| { |
| struct bt_conn *conn; |
| |
| if (err) { |
| LOG_DBG("Discovery failed (%d)", err); |
| return; |
| } |
| |
| LOG_DBG("Discovery done with %u AICS", |
| aics_count); |
| |
| bt_micp_mic_ctlr_conn_get(mic_ctlr, &conn); |
| |
| #if defined(CONFIG_BT_MICP_MIC_CTLR_AICS) |
| if (bt_micp_mic_ctlr_included_get(mic_ctlr, &micp_included) != 0) { |
| LOG_DBG("Could not get included services"); |
| memset(&micp_handles, 0, sizeof(micp_handles)); |
| } else { |
| aics_client_instance.aics_cnt = micp_included.aics_cnt; |
| aics_client_instance.aics = micp_included.aics; |
| bt_aics_client_cb_register(aics_client_instance.aics[0], &aics_client_cb); |
| |
| micp_handles.state_handle = micp_included.aics[0]->cli.state_handle; |
| micp_handles.gain_handle = micp_included.aics[0]->cli.gain_handle; |
| micp_handles.type_handle = micp_included.aics[0]->cli.type_handle; |
| micp_handles.status_handle = micp_included.aics[0]->cli.status_handle; |
| micp_handles.control_handle = micp_included.aics[0]->cli.control_handle; |
| micp_handles.desc_handle = micp_included.aics[0]->cli.desc_handle; |
| } |
| #endif /* CONFIG_BT_MICP_MIC_CTLR_AICS */ |
| |
| micp_handles.mute_handle = mic_ctlr->mute_handle; |
| btp_send_micp_found_ev(conn, err, &micp_handles); |
| } |
| |
| static struct bt_micp_mic_ctlr_cb micp_cbs = { |
| .discover = micp_mic_ctlr_discover_cb, |
| .mute = micp_mic_ctlr_mute_cb, |
| .mute_written = micp_mic_ctlr_mute_written_cb, |
| .unmute_written = micp_mic_ctlr_unmute_written_cb, |
| }; |
| |
| static uint8_t micp_supported_commands(const void *cmd, uint16_t cmd_len, void *rsp, |
| uint16_t *rsp_len) |
| { |
| struct btp_micp_read_supported_commands_rp *rp = rsp; |
| |
| /* octet 0 */ |
| tester_set_bit(rp->data, BTP_MICP_READ_SUPPORTED_COMMANDS); |
| tester_set_bit(rp->data, BTP_MICP_CTLR_DISCOVER); |
| tester_set_bit(rp->data, BTP_MICP_CTLR_MUTE_READ); |
| tester_set_bit(rp->data, BTP_MICP_CTLR_MUTE); |
| |
| *rsp_len = sizeof(*rp) + 1; |
| |
| return BTP_STATUS_SUCCESS; |
| } |
| |
| static uint8_t micp_discover(const void *cmd, uint16_t cmd_len, void *rsp, uint16_t *rsp_len) |
| { |
| const struct btp_micp_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_micp_mic_ctlr_discover(conn, &mic_ctlr); |
| if (err) { |
| LOG_DBG("Fail: %d", err); |
| return BTP_STATUS_FAILED; |
| } |
| |
| return BTP_STATUS_SUCCESS; |
| } |
| |
| static uint8_t micp_mute_read(const void *cmd, uint16_t cmd_len, void *rsp, uint16_t *rsp_len) |
| { |
| int err; |
| |
| LOG_DBG("Read mute"); |
| |
| err = bt_micp_mic_ctlr_mute_get(mic_ctlr); |
| if (err) { |
| return BTP_STATUS_FAILED; |
| } |
| |
| return BTP_STATUS_SUCCESS; |
| } |
| |
| static uint8_t micp_mute(const void *cmd, uint16_t cmd_len, void *rsp, uint16_t *rsp_len) |
| { |
| int err; |
| |
| LOG_DBG("MICP Mute"); |
| |
| err = bt_micp_mic_ctlr_mute(mic_ctlr); |
| if (err) { |
| return BTP_STATUS_FAILED; |
| } |
| |
| return BTP_STATUS_SUCCESS; |
| } |
| |
| static const struct btp_handler micp_handlers[] = { |
| { |
| .opcode = BTP_MICP_READ_SUPPORTED_COMMANDS, |
| .index = BTP_INDEX_NONE, |
| .expect_len = 0, |
| .func = micp_supported_commands, |
| }, |
| { |
| .opcode = BTP_MICP_CTLR_DISCOVER, |
| .expect_len = sizeof(struct btp_micp_discover_cmd), |
| .func = micp_discover, |
| }, |
| { |
| .opcode = BTP_MICP_CTLR_MUTE_READ, |
| .expect_len = sizeof(struct btp_micp_mute_read_cmd), |
| .func = micp_mute_read, |
| }, |
| { |
| .opcode = BTP_MICP_CTLR_MUTE, |
| .expect_len = sizeof(struct btp_micp_mute_cmd), |
| .func = micp_mute, |
| }, |
| }; |
| |
| uint8_t tester_init_micp(void) |
| { |
| int err; |
| |
| err = bt_micp_mic_ctlr_cb_register(&micp_cbs); |
| |
| if (err) { |
| LOG_DBG("Failed to register callbacks: %d", err); |
| return BTP_STATUS_FAILED; |
| } |
| |
| tester_register_command_handlers(BTP_SERVICE_ID_MICP, micp_handlers, |
| ARRAY_SIZE(micp_handlers)); |
| |
| return BTP_STATUS_SUCCESS; |
| } |
| |
| uint8_t tester_unregister_micp(void) |
| { |
| (void)bt_micp_mic_ctlr_cb_register(NULL); |
| return BTP_STATUS_SUCCESS; |
| } |
| |
| /* Microphone Control Service */ |
| static uint8_t mics_supported_commands(const void *cmd, uint16_t cmd_len, void *rsp, |
| uint16_t *rsp_len) |
| { |
| struct btp_mics_read_supported_commands_rp *rp = rsp; |
| |
| /* octet 0 */ |
| tester_set_bit(rp->data, BTP_MICS_READ_SUPPORTED_COMMANDS); |
| tester_set_bit(rp->data, BTP_MICS_DEV_MUTE_DISABLE); |
| tester_set_bit(rp->data, BTP_MICS_DEV_MUTE_READ); |
| tester_set_bit(rp->data, BTP_MICS_DEV_MUTE); |
| tester_set_bit(rp->data, BTP_MICS_DEV_UNMUTE); |
| |
| *rsp_len = sizeof(*rp) + 1; |
| |
| return BTP_STATUS_SUCCESS; |
| } |
| |
| static uint8_t mics_mute_disable(const void *cmd, uint16_t cmd_len, void *rsp, uint16_t *rsp_len) |
| { |
| int err; |
| |
| LOG_DBG("MICP Mute disable"); |
| |
| err = bt_micp_mic_dev_mute_disable(); |
| if (err) { |
| return BTP_STATUS_FAILED; |
| } |
| |
| return BTP_STATUS_SUCCESS; |
| } |
| |
| static uint8_t mics_mute_read(const void *cmd, uint16_t cmd_len, void *rsp, uint16_t *rsp_len) |
| { |
| int err; |
| |
| LOG_DBG("MICS Mute state read"); |
| |
| err = bt_micp_mic_dev_mute_get(); |
| if (err) { |
| return BTP_STATUS_FAILED; |
| } |
| |
| return BTP_STATUS_SUCCESS; |
| } |
| |
| static uint8_t mics_mute(const void *cmd, uint16_t cmd_len, void *rsp, uint16_t *rsp_len) |
| { |
| int err; |
| |
| LOG_DBG("MICS Mute"); |
| |
| err = bt_micp_mic_dev_mute(); |
| if (err) { |
| return BTP_STATUS_FAILED; |
| } |
| |
| return BTP_STATUS_SUCCESS; |
| } |
| |
| static uint8_t mics_unmute(const void *cmd, uint16_t cmd_len, void *rsp, uint16_t *rsp_len) |
| { |
| int err; |
| |
| LOG_DBG("MICS Mute"); |
| |
| err = bt_micp_mic_dev_unmute(); |
| if (err) { |
| return BTP_STATUS_FAILED; |
| } |
| |
| return BTP_STATUS_SUCCESS; |
| } |
| |
| static void btp_send_mics_mute_state_ev(uint8_t mute) |
| { |
| struct btp_mics_mute_state_ev ev; |
| |
| ev.mute = mute; |
| |
| tester_event(BTP_SERVICE_ID_MICS, BTP_MICS_MUTE_STATE_EV, &ev, sizeof(ev)); |
| } |
| |
| static void mic_dev_mute_cb(uint8_t mute) |
| { |
| LOG_DBG("Microphone Device Mute cb"); |
| |
| btp_send_mics_mute_state_ev(mute); |
| } |
| |
| static struct bt_micp_mic_dev_cb mic_dev_cb = { |
| .mute = mic_dev_mute_cb, |
| }; |
| |
| #if defined(CONFIG_BT_MICP_MIC_DEV_AICS) |
| static void aics_state_cb(struct bt_aics *inst, int err, int8_t gain, |
| uint8_t mute, uint8_t mode) |
| { |
| LOG_DBG("AICS state callback (%d)", err); |
| } |
| |
| static void aics_gain_setting_cb(struct bt_aics *inst, int err, uint8_t units, |
| int8_t minimum, int8_t maximum) |
| { |
| LOG_DBG("AICS gain setting callback (%d)", err); |
| } |
| |
| static void aics_input_type_cb(struct bt_aics *inst, int err, |
| uint8_t input_type) |
| { |
| LOG_DBG("AICS input type callback (%d)", err); |
| } |
| |
| static void aics_status_cb(struct bt_aics *inst, int err, bool active) |
| { |
| LOG_DBG("AICS status callback (%d)", err); |
| } |
| |
| static void aics_description_cb(struct bt_aics *inst, int err, |
| char *description) |
| { |
| LOG_DBG("AICS description callback (%d)", err); |
| } |
| |
| struct bt_aics_cb aics_mic_dev_cb = { |
| .state = aics_state_cb, |
| .gain_setting = aics_gain_setting_cb, |
| .type = aics_input_type_cb, |
| .status = aics_status_cb, |
| .description = aics_description_cb, |
| }; |
| #endif /* CONFIG_BT_MICP_MIC_DEV_AICS */ |
| |
| static const struct btp_handler mics_handlers[] = { |
| { |
| .opcode = BTP_MICS_READ_SUPPORTED_COMMANDS, |
| .index = BTP_INDEX_NONE, |
| .expect_len = 0, |
| .func = mics_supported_commands, |
| }, |
| { |
| .opcode = BTP_MICS_DEV_MUTE_DISABLE, |
| .expect_len = 0, |
| .func = mics_mute_disable, |
| }, |
| { |
| .opcode = BTP_MICS_DEV_MUTE_READ, |
| .expect_len = 0, |
| .func = mics_mute_read, |
| }, |
| { |
| .opcode = BTP_MICS_DEV_MUTE, |
| .expect_len = 0, |
| .func = mics_mute, |
| }, |
| { |
| .opcode = BTP_MICS_DEV_UNMUTE, |
| .expect_len = 0, |
| .func = mics_unmute, |
| }, |
| }; |
| |
| uint8_t tester_init_mics(void) |
| { |
| int err; |
| |
| memset(&mic_dev_register_param, 0, sizeof(mic_dev_register_param)); |
| |
| #if defined(CONFIG_BT_MICP_MIC_DEV_AICS) |
| char input_desc[CONFIG_BT_MICP_MIC_DEV_AICS_INSTANCE_COUNT][16]; |
| |
| for (size_t i = 0; i < ARRAY_SIZE(mic_dev_register_param.aics_param); i++) { |
| mic_dev_register_param.aics_param[i].desc_writable = true; |
| snprintf(input_desc[i], sizeof(input_desc[i]), |
| "Input %zu", i + 1); |
| mic_dev_register_param.aics_param[i].description = input_desc[i]; |
| mic_dev_register_param.aics_param[i].type = BT_AICS_INPUT_TYPE_DIGITAL; |
| mic_dev_register_param.aics_param[i].status = 1; |
| mic_dev_register_param.aics_param[i].gain_mode = BT_AICS_MODE_MANUAL; |
| mic_dev_register_param.aics_param[i].units = 1; |
| mic_dev_register_param.aics_param[i].min_gain = 0; |
| mic_dev_register_param.aics_param[i].max_gain = 100; |
| mic_dev_register_param.aics_param[i].cb = &aics_mic_dev_cb; |
| } |
| #endif /* CONFIG_BT_MICP_MIC_DEV_AICS */ |
| |
| mic_dev_register_param.cb = &mic_dev_cb; |
| |
| err = bt_micp_mic_dev_register(&mic_dev_register_param); |
| if (err) { |
| return BTP_STATUS_FAILED; |
| } |
| |
| #if defined(CONFIG_BT_MICP_MIC_DEV_AICS) |
| err = bt_micp_mic_dev_included_get(&micp_included); |
| if (err) { |
| return BTP_STATUS_FAILED; |
| } |
| #endif /* CONFIG_BT_MICP_MIC_DEV_AICS */ |
| |
| tester_register_command_handlers(BTP_SERVICE_ID_MICS, mics_handlers, |
| ARRAY_SIZE(mics_handlers)); |
| |
| return BTP_STATUS_SUCCESS; |
| } |
| |
| uint8_t tester_unregister_mics(void) |
| { |
| return BTP_STATUS_SUCCESS; |
| } |