According to AVCTP v1.4: On the controller and the target side, handling of transaction labels is dependent on the application. Signed-off-by: Zihao Gao <gaozihao@xiaomi.com>
310 lines
7.3 KiB
C
310 lines
7.3 KiB
C
/** @file
|
|
* @brief Audio Video Remote Control Profile shell functions.
|
|
*/
|
|
|
|
/*
|
|
* Copyright (c) 2024 Xiaomi InC.
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
#include <errno.h>
|
|
#include <zephyr/types.h>
|
|
#include <stddef.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <zephyr/sys/byteorder.h>
|
|
#include <zephyr/kernel.h>
|
|
|
|
#include <zephyr/settings/settings.h>
|
|
|
|
#include <zephyr/bluetooth/bluetooth.h>
|
|
#include <zephyr/bluetooth/classic/avrcp.h>
|
|
#include <zephyr/bluetooth/conn.h>
|
|
#include <zephyr/bluetooth/hci.h>
|
|
#include <zephyr/bluetooth/l2cap.h>
|
|
|
|
#include <zephyr/shell/shell.h>
|
|
|
|
#include "host/shell/bt.h"
|
|
#include "common/bt_shell_private.h"
|
|
|
|
struct bt_avrcp *default_avrcp;
|
|
static bool avrcp_registered;
|
|
static uint8_t local_tid;
|
|
|
|
static uint8_t get_next_tid(void)
|
|
{
|
|
uint8_t ret = local_tid;
|
|
|
|
local_tid++;
|
|
local_tid &= 0x0F;
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void avrcp_connected(struct bt_avrcp *avrcp)
|
|
{
|
|
bt_shell_print("AVRCP connected");
|
|
default_avrcp = avrcp;
|
|
local_tid = 0;
|
|
}
|
|
|
|
static void avrcp_disconnected(struct bt_avrcp *avrcp)
|
|
{
|
|
bt_shell_print("AVRCP disconnected");
|
|
local_tid = 0;
|
|
}
|
|
|
|
static void avrcp_get_cap_rsp(struct bt_avrcp *avrcp, uint8_t tid,
|
|
const struct bt_avrcp_get_cap_rsp *rsp)
|
|
{
|
|
uint8_t i;
|
|
|
|
switch (rsp->cap_id) {
|
|
case BT_AVRCP_CAP_COMPANY_ID:
|
|
for (i = 0; i < rsp->cap_cnt; i++) {
|
|
bt_shell_print("Remote CompanyID = 0x%06x",
|
|
sys_get_be24(&rsp->cap[BT_AVRCP_COMPANY_ID_SIZE * i]));
|
|
}
|
|
break;
|
|
case BT_AVRCP_CAP_EVENTS_SUPPORTED:
|
|
for (i = 0; i < rsp->cap_cnt; i++) {
|
|
bt_shell_print("Remote supported EventID = 0x%02x", rsp->cap[i]);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void avrcp_unit_info_rsp(struct bt_avrcp *avrcp, uint8_t tid,
|
|
struct bt_avrcp_unit_info_rsp *rsp)
|
|
{
|
|
bt_shell_print("AVRCP unit info received, unit type = 0x%02x, company_id = 0x%06x",
|
|
rsp->unit_type, rsp->company_id);
|
|
}
|
|
|
|
static void avrcp_subunit_info_rsp(struct bt_avrcp *avrcp, uint8_t tid,
|
|
struct bt_avrcp_subunit_info_rsp *rsp)
|
|
{
|
|
int i;
|
|
|
|
bt_shell_print("AVRCP subunit info received, subunit type = 0x%02x, extended subunit = %d",
|
|
rsp->subunit_type, rsp->max_subunit_id);
|
|
for (i = 0; i < rsp->max_subunit_id; i++) {
|
|
bt_shell_print("extended subunit id = %d, subunit type = 0x%02x",
|
|
rsp->extended_subunit_id[i], rsp->extended_subunit_type[i]);
|
|
}
|
|
}
|
|
|
|
static void avrcp_passthrough_rsp(struct bt_avrcp *avrcp, uint8_t tid, bt_avrcp_rsp_t result,
|
|
const struct bt_avrcp_passthrough_rsp *rsp)
|
|
{
|
|
if (result == BT_AVRCP_RSP_ACCEPTED) {
|
|
bt_shell_print(
|
|
"AVRCP passthough command accepted, operation id = 0x%02x, state = %d",
|
|
BT_AVRCP_PASSTHROUGH_GET_OPID(rsp), BT_AVRCP_PASSTHROUGH_GET_STATE(rsp));
|
|
} else {
|
|
bt_shell_print("AVRCP passthough command rejected, operation id = 0x%02x, state = "
|
|
"%d, response = %d",
|
|
BT_AVRCP_PASSTHROUGH_GET_OPID(rsp),
|
|
BT_AVRCP_PASSTHROUGH_GET_STATE(rsp), result);
|
|
}
|
|
}
|
|
|
|
static struct bt_avrcp_cb avrcp_cb = {
|
|
.connected = avrcp_connected,
|
|
.disconnected = avrcp_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,
|
|
};
|
|
|
|
static int register_cb(const struct shell *sh)
|
|
{
|
|
int err;
|
|
|
|
if (avrcp_registered) {
|
|
return 0;
|
|
}
|
|
|
|
err = bt_avrcp_register_cb(&avrcp_cb);
|
|
if (!err) {
|
|
avrcp_registered = true;
|
|
shell_print(sh, "AVRCP callbacks registered");
|
|
} else {
|
|
shell_print(sh, "failed to register callbacks");
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
static int cmd_register_cb(const struct shell *sh, int32_t argc, char *argv[])
|
|
{
|
|
if (avrcp_registered) {
|
|
shell_print(sh, "already registered");
|
|
return 0;
|
|
}
|
|
|
|
register_cb(sh);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int cmd_connect(const struct shell *sh, int32_t argc, char *argv[])
|
|
{
|
|
if (!avrcp_registered) {
|
|
if (register_cb(sh) != 0) {
|
|
return -ENOEXEC;
|
|
}
|
|
}
|
|
|
|
if (!default_conn) {
|
|
shell_error(sh, "BR/EDR not connected");
|
|
return -ENOEXEC;
|
|
}
|
|
|
|
default_avrcp = bt_avrcp_connect(default_conn);
|
|
if (NULL == default_avrcp) {
|
|
shell_error(sh, "fail to connect AVRCP");
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int cmd_disconnect(const struct shell *sh, int32_t argc, char *argv[])
|
|
{
|
|
if (!avrcp_registered) {
|
|
if (register_cb(sh) != 0) {
|
|
return -ENOEXEC;
|
|
}
|
|
}
|
|
|
|
if (default_avrcp != NULL) {
|
|
bt_avrcp_disconnect(default_avrcp);
|
|
default_avrcp = NULL;
|
|
} else {
|
|
shell_error(sh, "AVRCP is not connected");
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int cmd_get_unit_info(const struct shell *sh, int32_t argc, char *argv[])
|
|
{
|
|
if (!avrcp_registered) {
|
|
if (register_cb(sh) != 0) {
|
|
return -ENOEXEC;
|
|
}
|
|
}
|
|
|
|
if (default_avrcp != NULL) {
|
|
bt_avrcp_get_unit_info(default_avrcp, get_next_tid());
|
|
} else {
|
|
shell_error(sh, "AVRCP is not connected");
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int cmd_get_subunit_info(const struct shell *sh, int32_t argc, char *argv[])
|
|
{
|
|
if (!avrcp_registered) {
|
|
if (register_cb(sh) != 0) {
|
|
return -ENOEXEC;
|
|
}
|
|
}
|
|
|
|
if (default_avrcp != NULL) {
|
|
bt_avrcp_get_subunit_info(default_avrcp, get_next_tid());
|
|
} else {
|
|
shell_error(sh, "AVRCP is not connected");
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int cmd_passthrough(const struct shell *sh, bt_avrcp_opid_t opid, const uint8_t *payload,
|
|
uint8_t len)
|
|
{
|
|
if (!avrcp_registered) {
|
|
if (register_cb(sh) != 0) {
|
|
return -ENOEXEC;
|
|
}
|
|
}
|
|
|
|
if (default_avrcp != NULL) {
|
|
bt_avrcp_passthrough(default_avrcp, get_next_tid(), opid, BT_AVRCP_BUTTON_PRESSED,
|
|
payload, len);
|
|
bt_avrcp_passthrough(default_avrcp, get_next_tid(), opid, BT_AVRCP_BUTTON_RELEASED,
|
|
payload, len);
|
|
} else {
|
|
shell_error(sh, "AVRCP is not connected");
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int cmd_play(const struct shell *sh, int32_t argc, char *argv[])
|
|
{
|
|
return cmd_passthrough(sh, BT_AVRCP_OPID_PLAY, NULL, 0);
|
|
}
|
|
|
|
static int cmd_pause(const struct shell *sh, int32_t argc, char *argv[])
|
|
{
|
|
return cmd_passthrough(sh, BT_AVRCP_OPID_PAUSE, NULL, 0);
|
|
}
|
|
|
|
static int cmd_get_cap(const struct shell *sh, int32_t argc, char *argv[])
|
|
{
|
|
const char *cap_id;
|
|
|
|
if (!avrcp_registered) {
|
|
if (register_cb(sh) != 0) {
|
|
return -ENOEXEC;
|
|
}
|
|
}
|
|
|
|
if (default_avrcp == NULL) {
|
|
shell_error(sh, "AVRCP is not connected");
|
|
return 0;
|
|
}
|
|
|
|
cap_id = argv[1];
|
|
if (!strcmp(cap_id, "company")) {
|
|
bt_avrcp_get_cap(default_avrcp, get_next_tid(), BT_AVRCP_CAP_COMPANY_ID);
|
|
} else if (!strcmp(cap_id, "events")) {
|
|
bt_avrcp_get_cap(default_avrcp, get_next_tid(), BT_AVRCP_CAP_EVENTS_SUPPORTED);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
SHELL_STATIC_SUBCMD_SET_CREATE(
|
|
avrcp_cmds,
|
|
SHELL_CMD_ARG(register_cb, NULL, "register avrcp callbacks", cmd_register_cb, 1, 0),
|
|
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(get_unit, NULL, "get unit info", cmd_get_unit_info, 1, 0),
|
|
SHELL_CMD_ARG(get_subunit, NULL, "get subunit info", cmd_get_subunit_info, 1, 0),
|
|
SHELL_CMD_ARG(get_cap, NULL, "get capabilities <cap_id: company or events>", cmd_get_cap, 2,
|
|
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_SUBCMD_SET_END);
|
|
|
|
static int cmd_avrcp(const struct shell *sh, size_t argc, char **argv)
|
|
{
|
|
if (argc == 1) {
|
|
shell_help(sh);
|
|
/* sh returns 1 when help is printed */
|
|
return 1;
|
|
}
|
|
|
|
shell_error(sh, "%s unknown parameter: %s", argv[0], argv[1]);
|
|
|
|
return -ENOEXEC;
|
|
}
|
|
|
|
SHELL_CMD_ARG_REGISTER(avrcp, &avrcp_cmds, "Bluetooth AVRCP sh commands", cmd_avrcp, 1, 1);
|