Commit 9cfbed62 authored by Make Shi's avatar Make Shi Committed by Chris Friedt
Browse files

Bluetooth: Shell: AVRCP: Shell interface for browsing support



- Add shell interface for browsing connect and disconnect testing and
  SetBrowsedPlayer command testing.

Signed-off-by: default avatarMake Shi <make.shi@nxp.com>
parent 240ad5ce
Loading
Loading
Loading
Loading
+280 −0
Original line number Diff line number Diff line
@@ -29,6 +29,12 @@
#include "host/shell/bt.h"
#include "common/bt_shell_private.h"

NET_BUF_POOL_DEFINE(avrcp_tx_pool, CONFIG_BT_MAX_CONN,
		    BT_L2CAP_BUF_SIZE(CONFIG_BT_L2CAP_TX_MTU),
		    CONFIG_BT_CONN_TX_USER_DATA_SIZE, NULL);

#define FOLDER_NAME_HEX_BUF_LEN 80

struct bt_avrcp_ct *default_ct;
struct bt_avrcp_tg *default_tg;
static bool avrcp_ct_registered;
@@ -60,6 +66,16 @@ static void avrcp_ct_disconnected(struct bt_avrcp_ct *ct)
	default_ct = NULL;
}

static void avrcp_ct_browsing_connected(struct bt_conn *conn, struct bt_avrcp_ct *ct)
{
	bt_shell_print("AVRCP CT browsing connected");
}

static void avrcp_ct_browsing_disconnected(struct bt_avrcp_ct *ct)
{
	bt_shell_print("AVRCP CT browsing disconnected");
}

static void avrcp_get_cap_rsp(struct bt_avrcp_ct *ct, uint8_t tid,
			      const struct bt_avrcp_get_cap_rsp *rsp)
{
@@ -115,13 +131,70 @@ static void avrcp_passthrough_rsp(struct bt_avrcp_ct *ct, uint8_t tid, bt_avrcp_
	}
}

static void avrcp_browsed_player_rsp(struct bt_avrcp_ct *ct, uint8_t tid,
				     struct net_buf *buf)
{
	struct bt_avrcp_set_browsed_player_rsp *rsp;
	struct bt_avrcp_folder_name *folder_name;

	rsp = net_buf_pull_mem(buf, sizeof(*rsp));
	if (rsp->status != BT_AVRCP_STATUS_OPERATION_COMPLETED) {
		bt_shell_print("AVRCP set browsed player failed, tid = %d, status = 0x%02x",
			       tid, rsp->status);
		return;
	}

	bt_shell_print("AVRCP set browsed player success, tid = %d", tid);
	bt_shell_print("  UID Counter: %u", sys_be16_to_cpu(rsp->uid_counter));
	bt_shell_print("  Number of Items: %u", sys_be32_to_cpu(rsp->num_items));
	bt_shell_print("  Charset ID: 0x%04X", sys_be16_to_cpu(rsp->charset_id));
	bt_shell_print("  Folder Depth: %u", rsp->folder_depth);

	while (buf->len > 0) {
		if (buf->len < sizeof(struct bt_avrcp_folder_name)) {
			bt_shell_print("incompleted message");
			break;
		}
		folder_name = net_buf_pull_mem(buf, sizeof(struct bt_avrcp_folder_name));
		folder_name->folder_name_len = sys_be16_to_cpu(folder_name->folder_name_len);
		if (buf->len < folder_name->folder_name_len) {
			bt_shell_print("incompleted message for folder_name");
			break;
		}
		net_buf_pull_mem(buf, folder_name->folder_name_len);

		if (sys_be16_to_cpu(rsp->charset_id) == BT_AVRCP_CHARSET_UTF8) {
			bt_shell_print("Raw folder name:");
			for (int i = 0; i < folder_name->folder_name_len; i++) {
				bt_shell_print("%c", folder_name->folder_name[i]);
			}
		} else {
			bt_shell_print(" Get folder Name : ");
			bt_shell_hexdump(folder_name->folder_name, folder_name->folder_name_len);
		}
		if (rsp->folder_depth > 0) {
			rsp->folder_depth--;
		} else {
			bt_shell_warn("Folder depth is mismatched with received data");
			break;
		}
	}

	if (rsp->folder_depth > 0) {
		bt_shell_print("folder depth mismatch: expected 0, got %u", rsp->folder_depth);
	}
}

static struct bt_avrcp_ct_cb app_avrcp_ct_cb = {
	.connected = avrcp_ct_connected,
	.disconnected = avrcp_ct_disconnected,
	.browsing_connected = avrcp_ct_browsing_connected,
	.browsing_disconnected = avrcp_ct_browsing_disconnected,
	.get_cap_rsp = avrcp_get_cap_rsp,
	.unit_info_rsp = avrcp_unit_info_rsp,
	.subunit_info_rsp = avrcp_subunit_info_rsp,
	.passthrough_rsp = avrcp_passthrough_rsp,
	.browsed_player_rsp = avrcp_browsed_player_rsp,
};

static void avrcp_tg_connected(struct bt_conn *conn, struct bt_avrcp_tg *tg)
@@ -136,16 +209,36 @@ static void avrcp_tg_disconnected(struct bt_avrcp_tg *tg)
	default_tg = NULL;
}

static void avrcp_tg_browsing_connected(struct bt_conn *conn, struct bt_avrcp_tg *tg)
{
	bt_shell_print("AVRCP TG browsing connected");
}

static void avrcp_unit_info_req(struct bt_avrcp_tg *tg, uint8_t tid)
{
	bt_shell_print("AVRCP unit info request received");
	tg_tid = tid;
}

static void avrcp_tg_browsing_disconnected(struct bt_avrcp_tg *tg)
{
	bt_shell_print("AVRCP TG browsing disconnected");
}

static void avrcp_set_browsed_player_req(struct bt_avrcp_tg *tg, uint8_t tid,
					 uint16_t player_id)
{
	bt_shell_print("AVRCP set browsed player request received, player_id = %u", player_id);
	tg_tid = tid;
}

static struct bt_avrcp_tg_cb app_avrcp_tg_cb = {
	.connected = avrcp_tg_connected,
	.disconnected = avrcp_tg_disconnected,
	.browsing_connected = avrcp_tg_browsing_connected,
	.browsing_disconnected = avrcp_tg_browsing_disconnected,
	.unit_info_req = avrcp_unit_info_req,
	.set_browsed_player_req = avrcp_set_browsed_player_req,
};

static int register_ct_cb(const struct shell *sh)
@@ -256,6 +349,53 @@ static int cmd_disconnect(const struct shell *sh, int32_t argc, char *argv[])
	return 0;
}

static int cmd_browsing_connect(const struct shell *sh, int32_t argc, char *argv[])
{
	int err;

	if (!avrcp_ct_registered && register_ct_cb(sh) != 0) {
		return -ENOEXEC;
	}

	if (default_conn == NULL) {
		shell_error(sh, "BR/EDR not connected");
		return -ENOEXEC;
	}

	err = bt_avrcp_browsing_connect(default_conn);
	if (err < 0) {
		shell_error(sh, "fail to connect AVRCP browsing");
	} else {
		shell_print(sh, "AVRCP browsing connect request sent");
	}

	return err;
}

static int cmd_browsing_disconnect(const struct shell *sh, int32_t argc, char *argv[])
{
	int err;

	if (default_conn == NULL) {
		shell_print(sh, "Not connected");
		return -ENOEXEC;
	}

	if ((default_ct != NULL) || (default_tg != NULL)) {
		err = bt_avrcp_browsing_disconnect(default_conn);
		if (err < 0) {
			shell_error(sh, "fail to disconnect AVRCP browsing");
		} else {
			shell_print(sh, "AVRCP browsing disconnect request sent");
		}
	} else {
		shell_error(sh, "AVRCP is not connected");
		err = -ENOEXEC;
	}

	return err;
}

static int cmd_get_unit_info(const struct shell *sh, int32_t argc, char *argv[])
{
	if (!avrcp_ct_registered && register_ct_cb(sh) != 0) {
@@ -364,6 +504,139 @@ static int cmd_get_cap(const struct shell *sh, int32_t argc, char *argv[])
	return 0;
}

static int cmd_set_browsed_player(const struct shell *sh, int32_t argc, char *argv[])
{
	uint16_t player_id;
	int err;

	if (!avrcp_ct_registered && register_ct_cb(sh) != 0) {
		return -ENOEXEC;
	}

	if (default_ct == NULL) {
		shell_error(sh, "AVRCP is not connected");
		return -ENOEXEC;
	}

	player_id = (uint16_t)strtoul(argv[1], NULL, 0);

	err = bt_avrcp_ct_set_browsed_player(default_ct, get_next_tid(), player_id);
	if (err < 0) {
		shell_error(sh, "fail to set browsed player");
	} else {
		shell_print(sh, "AVRCP send set browsed player req");
	}

	return 0;
}

static int cmd_send_set_browsed_player_rsp(const struct shell *sh, int32_t argc, char *argv[])
{
	struct bt_avrcp_set_browsed_player_rsp *rsp;
	struct bt_avrcp_folder_name *folder_name;
	char *folder_name_str = "Music";
	uint8_t folder_name_hex[FOLDER_NAME_HEX_BUF_LEN];
	uint16_t folder_name_len = 0;
	struct net_buf *buf;
	uint16_t param_len;
	int err;

	if (!avrcp_tg_registered && register_tg_cb(sh) != 0) {
		return -ENOEXEC;
	}

	if (default_tg == NULL) {
		shell_error(sh, "AVRCP TG is not connected");
		return -ENOEXEC;
	}

	buf = bt_avrcp_create_pdu(&avrcp_tx_pool);
	if (buf == NULL) {
		shell_error(sh, "Failed to allocate buffer for AVRCP browsing response");
		return -ENOMEM;
	}

	if (net_buf_tailroom(buf) < sizeof(struct bt_avrcp_set_browsed_player_rsp)) {
		shell_error(sh, "Not enough tailroom in buffer for browsed player rsp");
		goto failed;
	}

	rsp = net_buf_add(buf, sizeof(*rsp));
	/* Set default rsp */
	rsp->status = BT_AVRCP_STATUS_OPERATION_COMPLETED;
	rsp->uid_counter = sys_cpu_to_be16(0x0001U);
	rsp->num_items = sys_cpu_to_be32(100U);
	rsp->charset_id = sys_cpu_to_be16(BT_AVRCP_CHARSET_UTF8);
	rsp->folder_depth = 1;

	/* Parse command line arguments or use default values */
	if (argc >= 2) {
		rsp->status = (uint8_t)strtoul(argv[1], NULL, 0);
	}

	if (argc >= 3) {
		rsp->uid_counter = sys_cpu_to_be16((uint16_t)strtoul(argv[2], NULL, 0));
	}

	if (argc >= 4) {
		rsp->num_items = sys_cpu_to_be32((uint32_t)strtoul(argv[3], NULL, 0));
	}

	if (argc >= 5) {
		rsp->charset_id = sys_cpu_to_be16((uint16_t)strtoul(argv[4], NULL, 0));
	}

	if (rsp->charset_id == sys_cpu_to_be16(BT_AVRCP_CHARSET_UTF8)) {
		if (argc >= 6) {
			folder_name_str = argv[5];
		}
		folder_name_len = strlen(folder_name_str);
	} else {
		if (argc >= 6) {
			folder_name_len = hex2bin(argv[5], strlen(argv[5]), folder_name_hex,
						  sizeof(folder_name_hex));
			if (folder_name_len == 0) {
				shell_error(sh, "Failed to get folder_name from  %s", argv[5]);
			}
		} else {
			shell_error(sh, "Please input hex string for folder_name");
			goto failed;
		}
	}

	param_len = folder_name_len + sizeof(struct bt_avrcp_folder_name);
	if (net_buf_tailroom(buf) < param_len) {
		shell_error(sh, "Not enough tailroom in buffer for param");
		goto failed;
	}

	folder_name = net_buf_add(buf, sizeof(*folder_name));
	folder_name->folder_name_len = sys_cpu_to_be16(folder_name_len);
	if (rsp->charset_id == sys_cpu_to_be16(BT_AVRCP_CHARSET_UTF8)) {
		net_buf_add_mem(buf, folder_name_str, folder_name_len);
	} else {
		net_buf_add_mem(buf, folder_name_hex, folder_name_len);
	}

	err = bt_avrcp_tg_send_set_browsed_player_rsp(default_tg, tg_tid, buf);
	if (err == 0) {
		shell_print(sh, "Send set browsed player response, status = 0x%02x", rsp->status);
	} else {
		shell_error(sh, "Failed to send set browsed player response, err = %d", err);
		goto failed;
	}

	return 0;
failed:
	net_buf_unref(buf);
	return -ENOEXEC;
}

#define HELP_BROWSED_PLAYER_RSP                                                      \
	"Send SetBrowsedPlayer response\n"					     \
	"Usage: send_browsed_player_rsp [status] [uid_counter] [num_items] "         \
	"[charset_id] [folder_name]"

SHELL_STATIC_SUBCMD_SET_CREATE(
	ct_cmds,
	SHELL_CMD_ARG(register_cb, NULL, "register avrcp ct callbacks", cmd_register_ct_cb, 1, 0),
@@ -373,12 +646,16 @@ SHELL_STATIC_SUBCMD_SET_CREATE(
		      0),
	SHELL_CMD_ARG(play, NULL, "request a play at the remote player", cmd_play, 1, 0),
	SHELL_CMD_ARG(pause, NULL, "request a pause at the remote player", cmd_pause, 1, 0),
	SHELL_CMD_ARG(set_browsed_player, NULL, "set browsed player <player_id>",
		      cmd_set_browsed_player, 2, 0),
	SHELL_SUBCMD_SET_END);

SHELL_STATIC_SUBCMD_SET_CREATE(
	tg_cmds,
	SHELL_CMD_ARG(register_cb, NULL, "register avrcp tg callbacks", cmd_register_tg_cb, 1, 0),
	SHELL_CMD_ARG(send_unit_rsp, NULL, "send unit info response", cmd_send_unit_info_rsp, 1, 0),
	SHELL_CMD_ARG(send_browsed_player_rsp, NULL, HELP_BROWSED_PLAYER_RSP,
		      cmd_send_set_browsed_player_rsp, 1, 5),
	SHELL_SUBCMD_SET_END);

static int cmd_avrcp(const struct shell *sh, size_t argc, char **argv)
@@ -398,6 +675,9 @@ SHELL_STATIC_SUBCMD_SET_CREATE(
	avrcp_cmds,
	SHELL_CMD_ARG(connect, NULL, "connect AVRCP", cmd_connect, 1, 0),
	SHELL_CMD_ARG(disconnect, NULL, "disconnect AVRCP", cmd_disconnect, 1, 0),
	SHELL_CMD_ARG(browsing_connect, NULL, "connect browsing AVRCP", cmd_browsing_connect, 1, 0),
	SHELL_CMD_ARG(browsing_disconnect, NULL, "disconnect browsing AVRCP",
		      cmd_browsing_disconnect, 1, 0),
	SHELL_CMD(ct, &ct_cmds, "AVRCP CT shell commands", cmd_avrcp),
	SHELL_CMD(tg, &tg_cmds, "AVRCP TG shell commands", cmd_avrcp),
	SHELL_SUBCMD_SET_END);