Add echo test shell commands, `register`, `unregister`, `req`, and `rsp`. Signed-off-by: Lyle Zhu <lyle.zhu@nxp.com>
1277 lines
32 KiB
C
1277 lines
32 KiB
C
/** @file
|
|
* @brief Bluetooth BR/EDR shell module
|
|
*
|
|
* Provide some Bluetooth shell commands that can be useful to applications.
|
|
*/
|
|
|
|
/*
|
|
* Copyright (c) 2018 Intel Corporation
|
|
*
|
|
* 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/hci.h>
|
|
#include <zephyr/bluetooth/bluetooth.h>
|
|
#include <zephyr/bluetooth/conn.h>
|
|
#include <zephyr/bluetooth/l2cap.h>
|
|
#include <zephyr/bluetooth/classic/rfcomm.h>
|
|
#include <zephyr/bluetooth/classic/sdp.h>
|
|
#include <zephyr/bluetooth/classic/l2cap_br.h>
|
|
|
|
#include <zephyr/shell/shell.h>
|
|
|
|
#include "host/shell/bt.h"
|
|
#include "common/bt_shell_private.h"
|
|
|
|
#if defined(CONFIG_BT_CONN)
|
|
/* Connection context for BR/EDR legacy pairing in sec mode 3 */
|
|
static struct bt_conn *pairing_conn;
|
|
#endif /* CONFIG_BT_CONN */
|
|
|
|
#define DATA_BREDR_MTU 200
|
|
|
|
NET_BUF_POOL_FIXED_DEFINE(data_tx_pool, 1, BT_L2CAP_SDU_BUF_SIZE(DATA_BREDR_MTU),
|
|
CONFIG_BT_CONN_TX_USER_DATA_SIZE, NULL);
|
|
NET_BUF_POOL_FIXED_DEFINE(data_rx_pool, 1, DATA_BREDR_MTU, 8, NULL);
|
|
|
|
#define SDP_CLIENT_USER_BUF_LEN 512
|
|
NET_BUF_POOL_FIXED_DEFINE(sdp_client_pool, CONFIG_BT_MAX_CONN, SDP_CLIENT_USER_BUF_LEN, 8, NULL);
|
|
|
|
static int cmd_auth_pincode(const struct shell *sh, size_t argc, char *argv[])
|
|
{
|
|
struct bt_conn *conn;
|
|
uint8_t max = 16U;
|
|
|
|
if (default_conn) {
|
|
conn = default_conn;
|
|
} else if (pairing_conn) {
|
|
conn = pairing_conn;
|
|
} else {
|
|
conn = NULL;
|
|
}
|
|
|
|
if (!conn) {
|
|
shell_print(sh, "Not connected");
|
|
return -ENOEXEC;
|
|
}
|
|
|
|
if (strlen(argv[1]) > max) {
|
|
shell_print(sh, "PIN code value invalid - enter max %u digits", max);
|
|
return -ENOEXEC;
|
|
}
|
|
|
|
shell_print(sh, "PIN code \"%s\" applied", argv[1]);
|
|
|
|
bt_conn_auth_pincode_entry(conn, argv[1]);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int cmd_connect(const struct shell *sh, size_t argc, char *argv[])
|
|
{
|
|
struct bt_conn *conn;
|
|
bt_addr_t addr;
|
|
int err;
|
|
|
|
err = bt_addr_from_str(argv[1], &addr);
|
|
if (err) {
|
|
shell_print(sh, "Invalid peer address (err %d)", err);
|
|
return -ENOEXEC;
|
|
}
|
|
|
|
conn = bt_conn_create_br(&addr, BT_BR_CONN_PARAM_DEFAULT);
|
|
if (!conn) {
|
|
shell_print(sh, "Connection failed");
|
|
return -ENOEXEC;
|
|
}
|
|
|
|
shell_print(sh, "Connection pending");
|
|
|
|
/* unref connection obj in advance as app user */
|
|
bt_conn_unref(conn);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void br_device_found(const bt_addr_t *addr, int8_t rssi, const uint8_t cod[3],
|
|
const uint8_t eir[240])
|
|
{
|
|
char br_addr[BT_ADDR_STR_LEN];
|
|
char name[239];
|
|
int len = 240;
|
|
|
|
(void)memset(name, 0, sizeof(name));
|
|
|
|
while (len) {
|
|
if (len < 2) {
|
|
break;
|
|
}
|
|
|
|
/* Look for early termination */
|
|
if (!eir[0]) {
|
|
break;
|
|
}
|
|
|
|
/* Check if field length is correct */
|
|
if (eir[0] > len - 1) {
|
|
break;
|
|
}
|
|
|
|
switch (eir[1]) {
|
|
case BT_DATA_NAME_SHORTENED:
|
|
case BT_DATA_NAME_COMPLETE:
|
|
if (eir[0] > sizeof(name) - 1) {
|
|
memcpy(name, &eir[2], sizeof(name) - 1);
|
|
} else {
|
|
memcpy(name, &eir[2], eir[0] - 1);
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
/* Parse next AD Structure */
|
|
len -= eir[0] + 1;
|
|
eir += eir[0] + 1;
|
|
}
|
|
|
|
bt_addr_to_str(addr, br_addr, sizeof(br_addr));
|
|
|
|
bt_shell_print("[DEVICE]: %s, RSSI %i %s", br_addr, rssi, name);
|
|
}
|
|
|
|
static struct bt_br_discovery_result br_discovery_results[5];
|
|
|
|
static void br_discovery_complete(const struct bt_br_discovery_result *results, size_t count)
|
|
{
|
|
size_t i;
|
|
|
|
bt_shell_print("BR/EDR discovery complete");
|
|
|
|
for (i = 0; i < count; i++) {
|
|
br_device_found(&results[i].addr, results[i].rssi, results[i].cod, results[i].eir);
|
|
}
|
|
}
|
|
|
|
static struct bt_br_discovery_cb discovery_cb = {
|
|
.recv = NULL,
|
|
.timeout = br_discovery_complete,
|
|
};
|
|
|
|
static int cmd_discovery(const struct shell *sh, size_t argc, char *argv[])
|
|
{
|
|
const char *action;
|
|
|
|
action = argv[1];
|
|
if (!strcmp(action, "on")) {
|
|
static bool reg_cb = true;
|
|
struct bt_br_discovery_param param;
|
|
|
|
param.limited = false;
|
|
param.length = 8U;
|
|
|
|
if (argc > 2) {
|
|
param.length = atoi(argv[2]);
|
|
}
|
|
|
|
if (argc > 3 && !strcmp(argv[3], "limited")) {
|
|
param.limited = true;
|
|
}
|
|
|
|
if (reg_cb) {
|
|
reg_cb = false;
|
|
bt_br_discovery_cb_register(&discovery_cb);
|
|
}
|
|
|
|
if (bt_br_discovery_start(¶m, br_discovery_results,
|
|
ARRAY_SIZE(br_discovery_results)) < 0) {
|
|
shell_print(sh, "Failed to start discovery");
|
|
return -ENOEXEC;
|
|
}
|
|
|
|
shell_print(sh, "Discovery started");
|
|
} else if (!strcmp(action, "off")) {
|
|
if (bt_br_discovery_stop()) {
|
|
shell_print(sh, "Failed to stop discovery");
|
|
return -ENOEXEC;
|
|
}
|
|
|
|
shell_print(sh, "Discovery stopped");
|
|
} else {
|
|
shell_help(sh);
|
|
return SHELL_CMD_HELP_PRINTED;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
struct bt_l2cap_br_server {
|
|
struct bt_l2cap_server server;
|
|
#if defined(CONFIG_BT_L2CAP_RET_FC)
|
|
uint8_t options;
|
|
#endif /* CONFIG_BT_L2CAP_RET_FC */
|
|
};
|
|
|
|
struct l2cap_br_chan {
|
|
struct bt_l2cap_br_chan chan;
|
|
#if defined(CONFIG_BT_L2CAP_RET_FC)
|
|
struct k_fifo l2cap_recv_fifo;
|
|
bool hold_credit;
|
|
#endif /* CONFIG_BT_L2CAP_RET_FC */
|
|
};
|
|
|
|
static int l2cap_recv(struct bt_l2cap_chan *chan, struct net_buf *buf)
|
|
{
|
|
struct l2cap_br_chan *br_chan = CONTAINER_OF(chan, struct l2cap_br_chan, chan.chan);
|
|
|
|
bt_shell_print("Incoming data channel %p len %u", chan, buf->len);
|
|
|
|
if (buf->len) {
|
|
bt_shell_hexdump(buf->data, buf->len);
|
|
}
|
|
|
|
#if defined(CONFIG_BT_L2CAP_RET_FC)
|
|
if (br_chan->hold_credit) {
|
|
k_fifo_put(&br_chan->l2cap_recv_fifo, buf);
|
|
return -EINPROGRESS;
|
|
}
|
|
#endif /* CONFIG_BT_L2CAP_RET_FC */
|
|
(void)br_chan;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void l2cap_connected(struct bt_l2cap_chan *chan)
|
|
{
|
|
struct l2cap_br_chan *br_chan = CONTAINER_OF(chan, struct l2cap_br_chan, chan.chan);
|
|
|
|
bt_shell_print("Channel %p connected", chan);
|
|
|
|
#if defined(CONFIG_BT_L2CAP_RET_FC)
|
|
switch (br_chan->chan.rx.mode) {
|
|
case BT_L2CAP_BR_LINK_MODE_BASIC:
|
|
bt_shell_print("It is basic mode");
|
|
if (br_chan->hold_credit) {
|
|
br_chan->hold_credit = false;
|
|
bt_shell_warn("hold_credit is unsupported in basic mode");
|
|
}
|
|
break;
|
|
case BT_L2CAP_BR_LINK_MODE_RET:
|
|
bt_shell_print("It is retransmission mode");
|
|
break;
|
|
case BT_L2CAP_BR_LINK_MODE_FC:
|
|
bt_shell_print("It is flow control mode");
|
|
break;
|
|
case BT_L2CAP_BR_LINK_MODE_ERET:
|
|
bt_shell_print("It is enhance retransmission mode");
|
|
break;
|
|
case BT_L2CAP_BR_LINK_MODE_STREAM:
|
|
bt_shell_print("It is streaming mode");
|
|
break;
|
|
default:
|
|
bt_shell_error("It is unknown mode");
|
|
break;
|
|
}
|
|
#endif /* CONFIG_BT_L2CAP_RET_FC */
|
|
(void)br_chan;
|
|
}
|
|
|
|
static void l2cap_disconnected(struct bt_l2cap_chan *chan)
|
|
{
|
|
#if defined(CONFIG_BT_L2CAP_RET_FC)
|
|
struct net_buf *buf;
|
|
struct l2cap_br_chan *br_chan = CONTAINER_OF(chan, struct l2cap_br_chan, chan.chan);
|
|
#endif /* CONFIG_BT_L2CAP_RET_FC */
|
|
|
|
bt_shell_print("Channel %p disconnected", chan);
|
|
|
|
#if defined(CONFIG_BT_L2CAP_RET_FC)
|
|
do {
|
|
buf = k_fifo_get(&br_chan->l2cap_recv_fifo, K_NO_WAIT);
|
|
if (buf != NULL) {
|
|
net_buf_unref(buf);
|
|
}
|
|
} while (buf != NULL);
|
|
#endif /* CONFIG_BT_L2CAP_RET_FC */
|
|
}
|
|
|
|
static struct net_buf *l2cap_alloc_buf(struct bt_l2cap_chan *chan)
|
|
{
|
|
bt_shell_print("Channel %p requires buffer", chan);
|
|
|
|
return net_buf_alloc(&data_rx_pool, K_NO_WAIT);
|
|
}
|
|
|
|
#if defined(CONFIG_BT_L2CAP_SEG_RECV)
|
|
static void seg_recv(struct bt_l2cap_chan *chan, size_t sdu_len, off_t seg_offset,
|
|
struct net_buf_simple *seg)
|
|
{
|
|
bt_shell_print("Incoming data channel %p SDU len %u offset %lu len %u", chan, sdu_len,
|
|
seg_offset, seg->len);
|
|
|
|
if (seg->len) {
|
|
bt_shell_hexdump(seg->data, seg->len);
|
|
}
|
|
}
|
|
#endif /* CONFIG_BT_L2CAP_SEG_RECV */
|
|
|
|
static const struct bt_l2cap_chan_ops l2cap_ops = {
|
|
.alloc_buf = l2cap_alloc_buf,
|
|
.recv = l2cap_recv,
|
|
.connected = l2cap_connected,
|
|
.disconnected = l2cap_disconnected,
|
|
#if defined(CONFIG_BT_L2CAP_SEG_RECV)
|
|
.seg_recv = seg_recv,
|
|
#endif /* CONFIG_BT_L2CAP_SEG_RECV */
|
|
};
|
|
|
|
#define BT_L2CAP_BR_SERVER_OPT_RET BIT(0)
|
|
#define BT_L2CAP_BR_SERVER_OPT_FC BIT(1)
|
|
#define BT_L2CAP_BR_SERVER_OPT_ERET BIT(2)
|
|
#define BT_L2CAP_BR_SERVER_OPT_STREAM BIT(3)
|
|
#define BT_L2CAP_BR_SERVER_OPT_MODE_OPTIONAL BIT(4)
|
|
#define BT_L2CAP_BR_SERVER_OPT_EXT_WIN_SIZE BIT(5)
|
|
#define BT_L2CAP_BR_SERVER_OPT_HOLD_CREDIT BIT(6)
|
|
|
|
static struct l2cap_br_chan l2cap_chan = {
|
|
.chan = {
|
|
.chan.ops = &l2cap_ops,
|
|
/* Set for now min. MTU */
|
|
.rx.mtu = DATA_BREDR_MTU,
|
|
},
|
|
#if defined(CONFIG_BT_L2CAP_RET_FC)
|
|
.l2cap_recv_fifo = Z_FIFO_INITIALIZER(l2cap_chan.l2cap_recv_fifo),
|
|
#endif /* CONFIG_BT_L2CAP_RET_FC */
|
|
};
|
|
|
|
static int l2cap_accept(struct bt_conn *conn, struct bt_l2cap_server *server,
|
|
struct bt_l2cap_chan **chan)
|
|
{
|
|
struct bt_l2cap_br_server *br_server;
|
|
|
|
br_server = CONTAINER_OF(server, struct bt_l2cap_br_server, server);
|
|
|
|
bt_shell_print("Incoming BR/EDR conn %p", conn);
|
|
|
|
if (l2cap_chan.chan.chan.conn) {
|
|
bt_shell_error("No channels available");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
*chan = &l2cap_chan.chan.chan;
|
|
|
|
#if defined(CONFIG_BT_L2CAP_RET_FC)
|
|
if (br_server->options & BT_L2CAP_BR_SERVER_OPT_HOLD_CREDIT) {
|
|
l2cap_chan.hold_credit = true;
|
|
} else {
|
|
l2cap_chan.hold_credit = false;
|
|
}
|
|
|
|
if (br_server->options & BT_L2CAP_BR_SERVER_OPT_EXT_WIN_SIZE) {
|
|
l2cap_chan.chan.rx.extended_control = true;
|
|
} else {
|
|
l2cap_chan.chan.rx.extended_control = false;
|
|
}
|
|
|
|
if (br_server->options & BT_L2CAP_BR_SERVER_OPT_MODE_OPTIONAL) {
|
|
l2cap_chan.chan.rx.optional = true;
|
|
} else {
|
|
l2cap_chan.chan.rx.optional = false;
|
|
}
|
|
|
|
l2cap_chan.chan.rx.fcs = BT_L2CAP_BR_FCS_16BIT;
|
|
|
|
if (br_server->options & BT_L2CAP_BR_SERVER_OPT_STREAM) {
|
|
l2cap_chan.chan.rx.mode = BT_L2CAP_BR_LINK_MODE_STREAM;
|
|
l2cap_chan.chan.rx.max_window = CONFIG_BT_L2CAP_MAX_WINDOW_SIZE;
|
|
l2cap_chan.chan.rx.max_transmit = 0;
|
|
} else if (br_server->options & BT_L2CAP_BR_SERVER_OPT_ERET) {
|
|
l2cap_chan.chan.rx.mode = BT_L2CAP_BR_LINK_MODE_ERET;
|
|
l2cap_chan.chan.rx.max_window = CONFIG_BT_L2CAP_MAX_WINDOW_SIZE;
|
|
l2cap_chan.chan.rx.max_transmit = 3;
|
|
} else if (br_server->options & BT_L2CAP_BR_SERVER_OPT_FC) {
|
|
l2cap_chan.chan.rx.mode = BT_L2CAP_BR_LINK_MODE_FC;
|
|
l2cap_chan.chan.rx.max_window = CONFIG_BT_L2CAP_MAX_WINDOW_SIZE;
|
|
l2cap_chan.chan.rx.max_transmit = 3;
|
|
} else if (br_server->options & BT_L2CAP_BR_SERVER_OPT_RET) {
|
|
l2cap_chan.chan.rx.mode = BT_L2CAP_BR_LINK_MODE_RET;
|
|
l2cap_chan.chan.rx.max_window = CONFIG_BT_L2CAP_MAX_WINDOW_SIZE;
|
|
l2cap_chan.chan.rx.max_transmit = 3;
|
|
}
|
|
#endif /* CONFIG_BT_L2CAP_RET_FC */
|
|
(void)br_server;
|
|
return 0;
|
|
}
|
|
|
|
static struct bt_l2cap_br_server l2cap_server = {
|
|
.server = {
|
|
.accept = l2cap_accept,
|
|
},
|
|
};
|
|
|
|
static int cmd_l2cap_register(const struct shell *sh, size_t argc, char *argv[])
|
|
{
|
|
if (l2cap_server.server.psm) {
|
|
shell_print(sh, "Already registered");
|
|
return -ENOEXEC;
|
|
}
|
|
|
|
l2cap_server.server.psm = strtoul(argv[1], NULL, 16);
|
|
|
|
#if defined(CONFIG_BT_L2CAP_RET_FC)
|
|
l2cap_server.options = 0;
|
|
|
|
if (!strcmp(argv[2], "none")) {
|
|
/* Support mode: None */
|
|
} else if (!strcmp(argv[2], "ret")) {
|
|
l2cap_server.options |= BT_L2CAP_BR_SERVER_OPT_RET;
|
|
} else if (!strcmp(argv[2], "fc")) {
|
|
l2cap_server.options |= BT_L2CAP_BR_SERVER_OPT_FC;
|
|
} else if (!strcmp(argv[2], "eret")) {
|
|
l2cap_server.options |= BT_L2CAP_BR_SERVER_OPT_ERET;
|
|
} else if (!strcmp(argv[2], "stream")) {
|
|
l2cap_server.options |= BT_L2CAP_BR_SERVER_OPT_STREAM;
|
|
} else {
|
|
l2cap_server.server.psm = 0;
|
|
shell_help(sh);
|
|
return SHELL_CMD_HELP_PRINTED;
|
|
}
|
|
|
|
for (size_t index = 3; index < argc; index++) {
|
|
if (!strcmp(argv[index], "hold_credit")) {
|
|
l2cap_server.options |= BT_L2CAP_BR_SERVER_OPT_HOLD_CREDIT;
|
|
} else if (!strcmp(argv[index], "mode_optional")) {
|
|
l2cap_server.options |= BT_L2CAP_BR_SERVER_OPT_MODE_OPTIONAL;
|
|
} else if (!strcmp(argv[index], "extended_control")) {
|
|
l2cap_server.options |= BT_L2CAP_BR_SERVER_OPT_EXT_WIN_SIZE;
|
|
} else {
|
|
l2cap_server.server.psm = 0;
|
|
shell_help(sh);
|
|
return SHELL_CMD_HELP_PRINTED;
|
|
}
|
|
}
|
|
|
|
if ((l2cap_server.options & BT_L2CAP_BR_SERVER_OPT_EXT_WIN_SIZE) &&
|
|
(!(l2cap_server.options &
|
|
(BT_L2CAP_BR_SERVER_OPT_ERET | BT_L2CAP_BR_SERVER_OPT_STREAM)))) {
|
|
shell_error(sh, "[extended_control] only supports mode eret and stream");
|
|
l2cap_server.server.psm = 0U;
|
|
return -ENOEXEC;
|
|
}
|
|
#endif /* CONFIG_BT_L2CAP_RET_FC */
|
|
|
|
if (bt_l2cap_br_server_register(&l2cap_server.server) < 0) {
|
|
shell_error(sh, "Unable to register psm");
|
|
l2cap_server.server.psm = 0U;
|
|
return -ENOEXEC;
|
|
}
|
|
|
|
shell_print(sh, "L2CAP psm %u registered", l2cap_server.server.psm);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int cmd_l2cap_connect(const struct shell *sh, size_t argc, char *argv[])
|
|
{
|
|
uint16_t psm;
|
|
struct bt_conn_info info;
|
|
int err;
|
|
|
|
if (!default_conn) {
|
|
shell_error(sh, "Not connected");
|
|
return -ENOEXEC;
|
|
}
|
|
|
|
if (l2cap_chan.chan.chan.conn) {
|
|
bt_shell_error("No channels available");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
err = bt_conn_get_info(default_conn, &info);
|
|
if ((err < 0) || (info.type != BT_CONN_TYPE_BR)) {
|
|
shell_error(sh, "Invalid conn type");
|
|
return -ENOEXEC;
|
|
}
|
|
|
|
psm = strtoul(argv[1], NULL, 16);
|
|
|
|
#if defined(CONFIG_BT_L2CAP_RET_FC)
|
|
if (!strcmp(argv[2], "none")) {
|
|
l2cap_chan.chan.rx.mode = BT_L2CAP_BR_LINK_MODE_BASIC;
|
|
} else if (!strcmp(argv[2], "ret")) {
|
|
l2cap_chan.chan.rx.mode = BT_L2CAP_BR_LINK_MODE_RET;
|
|
l2cap_chan.chan.rx.max_transmit = 3;
|
|
} else if (!strcmp(argv[2], "fc")) {
|
|
l2cap_chan.chan.rx.mode = BT_L2CAP_BR_LINK_MODE_FC;
|
|
l2cap_chan.chan.rx.max_transmit = 3;
|
|
} else if (!strcmp(argv[2], "eret")) {
|
|
l2cap_chan.chan.rx.mode = BT_L2CAP_BR_LINK_MODE_ERET;
|
|
l2cap_chan.chan.rx.max_transmit = 3;
|
|
} else if (!strcmp(argv[2], "stream")) {
|
|
l2cap_chan.chan.rx.mode = BT_L2CAP_BR_LINK_MODE_STREAM;
|
|
l2cap_chan.chan.rx.max_transmit = 0;
|
|
} else {
|
|
shell_help(sh);
|
|
return SHELL_CMD_HELP_PRINTED;
|
|
}
|
|
|
|
l2cap_chan.hold_credit = false;
|
|
l2cap_chan.chan.rx.optional = false;
|
|
l2cap_chan.chan.rx.extended_control = false;
|
|
|
|
for (size_t index = 3; index < argc; index++) {
|
|
if (!strcmp(argv[index], "hold_credit")) {
|
|
l2cap_chan.hold_credit = true;
|
|
} else if (!strcmp(argv[index], "mode_optional")) {
|
|
l2cap_chan.chan.rx.optional = true;
|
|
} else if (!strcmp(argv[index], "extended_control")) {
|
|
l2cap_chan.chan.rx.extended_control = true;
|
|
} else {
|
|
shell_help(sh);
|
|
return SHELL_CMD_HELP_PRINTED;
|
|
}
|
|
}
|
|
|
|
if ((l2cap_chan.chan.rx.extended_control) &&
|
|
((l2cap_chan.chan.rx.mode != BT_L2CAP_BR_LINK_MODE_ERET) &&
|
|
(l2cap_chan.chan.rx.mode != BT_L2CAP_BR_LINK_MODE_STREAM))) {
|
|
shell_error(sh, "[extended_control] only supports mode eret and stream");
|
|
return -ENOEXEC;
|
|
}
|
|
|
|
if (l2cap_chan.hold_credit && (l2cap_chan.chan.rx.mode == BT_L2CAP_BR_LINK_MODE_BASIC)) {
|
|
shell_error(sh, "[hold_credit] cannot support basic mode");
|
|
return -ENOEXEC;
|
|
}
|
|
|
|
l2cap_chan.chan.rx.max_window = CONFIG_BT_L2CAP_MAX_WINDOW_SIZE;
|
|
#endif /* CONFIG_BT_L2CAP_RET_FC */
|
|
|
|
err = bt_l2cap_chan_connect(default_conn, &l2cap_chan.chan.chan, psm);
|
|
if (err < 0) {
|
|
shell_error(sh, "Unable to connect to psm %u (err %d)", psm, err);
|
|
} else {
|
|
shell_print(sh, "L2CAP connection pending");
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
static int cmd_l2cap_disconnect(const struct shell *sh, size_t argc, char *argv[])
|
|
{
|
|
int err;
|
|
|
|
err = bt_l2cap_chan_disconnect(&l2cap_chan.chan.chan);
|
|
if (err) {
|
|
shell_error(sh, "Unable to disconnect: %u", -err);
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
static int cmd_l2cap_send(const struct shell *sh, size_t argc, char *argv[])
|
|
{
|
|
static uint8_t buf_data[DATA_BREDR_MTU];
|
|
int err, len = DATA_BREDR_MTU, count = 1;
|
|
struct net_buf *buf;
|
|
|
|
if (argc > 1) {
|
|
count = strtoul(argv[1], NULL, 10);
|
|
}
|
|
|
|
if (argc > 2) {
|
|
len = strtoul(argv[2], NULL, 10);
|
|
if (len > DATA_BREDR_MTU) {
|
|
shell_error(sh, "Length exceeds TX MTU for the channel");
|
|
return -ENOEXEC;
|
|
}
|
|
}
|
|
|
|
len = MIN(l2cap_chan.chan.tx.mtu, len);
|
|
|
|
while (count--) {
|
|
shell_print(sh, "Rem %d", count);
|
|
buf = net_buf_alloc(&data_tx_pool, K_SECONDS(2));
|
|
if (!buf) {
|
|
if (l2cap_chan.chan.state != BT_L2CAP_CONNECTED) {
|
|
shell_error(sh, "Channel disconnected, stopping TX");
|
|
|
|
return -EAGAIN;
|
|
}
|
|
shell_error(sh, "Allocation timeout, stopping TX");
|
|
|
|
return -EAGAIN;
|
|
}
|
|
net_buf_reserve(buf, BT_L2CAP_CHAN_SEND_RESERVE);
|
|
memset(buf_data, count, sizeof(buf_data));
|
|
|
|
net_buf_add_mem(buf, buf_data, len);
|
|
err = bt_l2cap_chan_send(&l2cap_chan.chan.chan, buf);
|
|
if (err < 0) {
|
|
shell_error(sh, "Unable to send: %d", -err);
|
|
net_buf_unref(buf);
|
|
return -ENOEXEC;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
#if defined(CONFIG_BT_L2CAP_RET_FC)
|
|
static int cmd_l2cap_credits(const struct shell *sh, size_t argc, char *argv[])
|
|
{
|
|
int err;
|
|
struct net_buf *buf;
|
|
|
|
buf = k_fifo_get(&l2cap_chan.l2cap_recv_fifo, K_NO_WAIT);
|
|
if (buf != NULL) {
|
|
err = bt_l2cap_chan_recv_complete(&l2cap_chan.chan.chan, buf);
|
|
if (err < 0) {
|
|
shell_error(sh, "Unable to set recv_complete: %d", -err);
|
|
}
|
|
} else {
|
|
shell_warn(sh, "No pending recv buffer");
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
#endif /* CONFIG_BT_L2CAP_RET_FC */
|
|
|
|
static void l2cap_br_echo_req(struct bt_conn *conn, uint8_t identifier, struct net_buf *buf)
|
|
{
|
|
bt_shell_print("Incoming ECHO REQ data identifier %u len %u", identifier, buf->len);
|
|
|
|
if (buf->len > 0) {
|
|
bt_shell_hexdump(buf->data, buf->len);
|
|
}
|
|
}
|
|
|
|
static void l2cap_br_echo_rsp(struct bt_conn *conn, struct net_buf *buf)
|
|
{
|
|
bt_shell_print("Incoming ECHO RSP data len %u", buf->len);
|
|
|
|
if (buf->len > 0) {
|
|
bt_shell_hexdump(buf->data, buf->len);
|
|
}
|
|
}
|
|
|
|
static struct bt_l2cap_br_echo_cb echo_cb = {
|
|
.req = l2cap_br_echo_req,
|
|
.rsp = l2cap_br_echo_rsp,
|
|
};
|
|
|
|
static int cmd_l2cap_echo_reg(const struct shell *sh, size_t argc, char *argv[])
|
|
{
|
|
int err;
|
|
|
|
err = bt_l2cap_br_echo_cb_register(&echo_cb);
|
|
if (err) {
|
|
shell_error(sh, "Failed to register echo callback: %d", -err);
|
|
return err;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int cmd_l2cap_echo_unreg(const struct shell *sh, size_t argc, char *argv[])
|
|
{
|
|
int err;
|
|
|
|
err = bt_l2cap_br_echo_cb_unregister(&echo_cb);
|
|
if (err) {
|
|
shell_error(sh, "Failed to unregister echo callback: %d", -err);
|
|
return err;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int cmd_l2cap_echo_req(const struct shell *sh, size_t argc, char *argv[])
|
|
{
|
|
static uint8_t buf_data[DATA_BREDR_MTU];
|
|
int err, len = DATA_BREDR_MTU;
|
|
struct net_buf *buf;
|
|
|
|
len = strtoul(argv[1], NULL, 10);
|
|
if (len > DATA_BREDR_MTU) {
|
|
shell_error(sh, "Length exceeds TX MTU for the channel");
|
|
return -ENOEXEC;
|
|
}
|
|
|
|
buf = net_buf_alloc(&data_tx_pool, K_SECONDS(2));
|
|
if (!buf) {
|
|
shell_error(sh, "Allocation timeout, stopping TX");
|
|
return -EAGAIN;
|
|
}
|
|
net_buf_reserve(buf, BT_L2CAP_BR_ECHO_REQ_RESERVE);
|
|
for (int i = 0; i < len; i++) {
|
|
buf_data[i] = (uint8_t)i;
|
|
}
|
|
|
|
net_buf_add_mem(buf, buf_data, len);
|
|
err = bt_l2cap_br_echo_req(default_conn, buf);
|
|
if (err < 0) {
|
|
shell_error(sh, "Unable to send ECHO REQ: %d", -err);
|
|
net_buf_unref(buf);
|
|
return -ENOEXEC;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int cmd_l2cap_echo_rsp(const struct shell *sh, size_t argc, char *argv[])
|
|
{
|
|
static uint8_t buf_data[DATA_BREDR_MTU];
|
|
int err, len = DATA_BREDR_MTU;
|
|
uint8_t identifier;
|
|
struct net_buf *buf;
|
|
|
|
identifier = (uint8_t)strtoul(argv[1], NULL, 10);
|
|
|
|
len = strtoul(argv[2], NULL, 10);
|
|
if (len > DATA_BREDR_MTU) {
|
|
shell_error(sh, "Length exceeds TX MTU for the channel");
|
|
return -ENOEXEC;
|
|
}
|
|
|
|
buf = net_buf_alloc(&data_tx_pool, K_SECONDS(2));
|
|
if (!buf) {
|
|
shell_error(sh, "Allocation timeout, stopping TX");
|
|
return -EAGAIN;
|
|
}
|
|
net_buf_reserve(buf, BT_L2CAP_BR_ECHO_RSP_RESERVE);
|
|
for (int i = 0; i < len; i++) {
|
|
buf_data[i] = (uint8_t)i;
|
|
}
|
|
|
|
net_buf_add_mem(buf, buf_data, len);
|
|
err = bt_l2cap_br_echo_rsp(default_conn, identifier, buf);
|
|
if (err < 0) {
|
|
shell_error(sh, "Unable to send ECHO RSP: %d", -err);
|
|
net_buf_unref(buf);
|
|
return -ENOEXEC;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int cmd_discoverable(const struct shell *sh, size_t argc, char *argv[])
|
|
{
|
|
int err = 0;
|
|
bool enable;
|
|
bool limited = false;
|
|
|
|
enable = shell_strtobool(argv[1], 10, &err);
|
|
if (err) {
|
|
shell_help(sh);
|
|
return SHELL_CMD_HELP_PRINTED;
|
|
}
|
|
|
|
if (argc > 2 && !strcmp(argv[2], "limited")) {
|
|
limited = true;
|
|
}
|
|
|
|
err = bt_br_set_discoverable(enable, limited);
|
|
if (err) {
|
|
shell_print(sh, "BR/EDR set/reset discoverable failed (err %d)", err);
|
|
return -ENOEXEC;
|
|
}
|
|
|
|
shell_print(sh, "BR/EDR set/reset discoverable done");
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int cmd_connectable(const struct shell *sh, size_t argc, char *argv[])
|
|
{
|
|
int err;
|
|
const char *action;
|
|
|
|
action = argv[1];
|
|
|
|
if (!strcmp(action, "on")) {
|
|
err = bt_br_set_connectable(true);
|
|
} else if (!strcmp(action, "off")) {
|
|
err = bt_br_set_connectable(false);
|
|
} else {
|
|
shell_help(sh);
|
|
return SHELL_CMD_HELP_PRINTED;
|
|
}
|
|
|
|
if (err) {
|
|
shell_print(sh, "BR/EDR set/rest connectable failed (err %d)", err);
|
|
return -ENOEXEC;
|
|
}
|
|
|
|
shell_print(sh, "BR/EDR set/reset connectable done");
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int cmd_oob(const struct shell *sh, size_t argc, char *argv[])
|
|
{
|
|
char addr[BT_ADDR_STR_LEN];
|
|
struct bt_br_oob oob;
|
|
int err;
|
|
|
|
err = bt_br_oob_get_local(&oob);
|
|
if (err) {
|
|
shell_print(sh, "BR/EDR OOB data failed");
|
|
return -ENOEXEC;
|
|
}
|
|
|
|
bt_addr_to_str(&oob.addr, addr, sizeof(addr));
|
|
|
|
shell_print(sh, "BR/EDR OOB data:");
|
|
shell_print(sh, " addr %s", addr);
|
|
return 0;
|
|
}
|
|
|
|
static uint8_t sdp_hfp_ag_user(struct bt_conn *conn, struct bt_sdp_client_result *result,
|
|
const struct bt_sdp_discover_params *params)
|
|
{
|
|
char addr[BT_ADDR_STR_LEN];
|
|
uint16_t param, version;
|
|
uint16_t features;
|
|
int err;
|
|
|
|
conn_addr_str(conn, addr, sizeof(addr));
|
|
|
|
if (result && result->resp_buf) {
|
|
bt_shell_print("SDP HFPAG data@%p (len %u) hint %u from remote %s",
|
|
result->resp_buf, result->resp_buf->len, result->next_record_hint,
|
|
addr);
|
|
|
|
/*
|
|
* Focus to get BT_SDP_ATTR_PROTO_DESC_LIST attribute item to
|
|
* get HFPAG Server Channel Number operating on RFCOMM protocol.
|
|
*/
|
|
err = bt_sdp_get_proto_param(result->resp_buf, BT_SDP_PROTO_RFCOMM, ¶m);
|
|
if (err < 0) {
|
|
bt_shell_error("Error getting Server CN, err %d", err);
|
|
goto done;
|
|
}
|
|
bt_shell_print("HFPAG Server CN param 0x%04x", param);
|
|
|
|
err = bt_sdp_get_profile_version(result->resp_buf, BT_SDP_HANDSFREE_SVCLASS,
|
|
&version);
|
|
if (err < 0) {
|
|
bt_shell_error("Error getting profile version, err %d", err);
|
|
goto done;
|
|
}
|
|
bt_shell_print("HFP version param 0x%04x", version);
|
|
|
|
/*
|
|
* Focus to get BT_SDP_ATTR_SUPPORTED_FEATURES attribute item to
|
|
* get profile Supported Features mask.
|
|
*/
|
|
err = bt_sdp_get_features(result->resp_buf, &features);
|
|
if (err < 0) {
|
|
bt_shell_error("Error getting HFPAG Features, err %d", err);
|
|
goto done;
|
|
}
|
|
bt_shell_print("HFPAG Supported Features param 0x%04x", features);
|
|
} else {
|
|
bt_shell_print("No SDP HFPAG data from remote %s", addr);
|
|
}
|
|
done:
|
|
return BT_SDP_DISCOVER_UUID_CONTINUE;
|
|
}
|
|
|
|
static uint8_t sdp_hfp_hf_user(struct bt_conn *conn,
|
|
struct bt_sdp_client_result *result,
|
|
const struct bt_sdp_discover_params *params)
|
|
{
|
|
char addr[BT_ADDR_STR_LEN];
|
|
uint16_t param, version;
|
|
uint16_t features;
|
|
int err;
|
|
|
|
conn_addr_str(conn, addr, sizeof(addr));
|
|
|
|
if (result && result->resp_buf) {
|
|
bt_shell_print("SDP HFPHF data@%p (len %u) hint %u from remote %s",
|
|
result->resp_buf, result->resp_buf->len, result->next_record_hint,
|
|
addr);
|
|
|
|
/*
|
|
* Focus to get BT_SDP_ATTR_PROTO_DESC_LIST attribute item to
|
|
* get HFPHF Server Channel Number operating on RFCOMM protocol.
|
|
*/
|
|
err = bt_sdp_get_proto_param(result->resp_buf, BT_SDP_PROTO_RFCOMM, ¶m);
|
|
if (err < 0) {
|
|
bt_shell_error("Error getting Server CN, err %d", err);
|
|
goto done;
|
|
}
|
|
bt_shell_print("HFPHF Server CN param 0x%04x", param);
|
|
|
|
err = bt_sdp_get_profile_version(result->resp_buf, BT_SDP_HANDSFREE_SVCLASS,
|
|
&version);
|
|
if (err < 0) {
|
|
bt_shell_error("Error getting profile version, err %d", err);
|
|
goto done;
|
|
}
|
|
bt_shell_print("HFP version param 0x%04x", version);
|
|
|
|
/*
|
|
* Focus to get BT_SDP_ATTR_SUPPORTED_FEATURES attribute item to
|
|
* get profile Supported Features mask.
|
|
*/
|
|
err = bt_sdp_get_features(result->resp_buf, &features);
|
|
if (err < 0) {
|
|
bt_shell_error("Error getting HFPHF Features, err %d", err);
|
|
goto done;
|
|
}
|
|
bt_shell_print("HFPHF Supported Features param 0x%04x", features);
|
|
} else {
|
|
bt_shell_print("No SDP HFPHF data from remote %s", addr);
|
|
}
|
|
done:
|
|
return BT_SDP_DISCOVER_UUID_CONTINUE;
|
|
}
|
|
|
|
static uint8_t sdp_a2src_user(struct bt_conn *conn, struct bt_sdp_client_result *result,
|
|
const struct bt_sdp_discover_params *params)
|
|
{
|
|
char addr[BT_ADDR_STR_LEN];
|
|
uint16_t param, version;
|
|
uint16_t features;
|
|
int err;
|
|
|
|
conn_addr_str(conn, addr, sizeof(addr));
|
|
|
|
if (result && result->resp_buf) {
|
|
bt_shell_print("SDP A2SRC data@%p (len %u) hint %u from remote %s",
|
|
result->resp_buf, result->resp_buf->len, result->next_record_hint,
|
|
addr);
|
|
|
|
/*
|
|
* Focus to get BT_SDP_ATTR_PROTO_DESC_LIST attribute item to
|
|
* get A2SRC Server PSM Number.
|
|
*/
|
|
err = bt_sdp_get_proto_param(result->resp_buf, BT_SDP_PROTO_L2CAP, ¶m);
|
|
if (err < 0) {
|
|
bt_shell_error("A2SRC PSM Number not found, err %d", err);
|
|
goto done;
|
|
}
|
|
|
|
bt_shell_print("A2SRC Server PSM Number param 0x%04x", param);
|
|
|
|
/*
|
|
* Focus to get BT_SDP_ATTR_PROFILE_DESC_LIST attribute item to
|
|
* get profile version number.
|
|
*/
|
|
err = bt_sdp_get_profile_version(result->resp_buf, BT_SDP_ADVANCED_AUDIO_SVCLASS,
|
|
&version);
|
|
if (err < 0) {
|
|
bt_shell_error("A2SRC version not found, err %d", err);
|
|
goto done;
|
|
}
|
|
bt_shell_print("A2SRC version param 0x%04x", version);
|
|
|
|
/*
|
|
* Focus to get BT_SDP_ATTR_SUPPORTED_FEATURES attribute item to
|
|
* get profile supported features mask.
|
|
*/
|
|
err = bt_sdp_get_features(result->resp_buf, &features);
|
|
if (err < 0) {
|
|
bt_shell_error("A2SRC Features not found, err %d", err);
|
|
goto done;
|
|
}
|
|
bt_shell_print("A2SRC Supported Features param 0x%04x", features);
|
|
} else {
|
|
bt_shell_print("No SDP A2SRC data from remote %s", addr);
|
|
}
|
|
done:
|
|
return BT_SDP_DISCOVER_UUID_CONTINUE;
|
|
}
|
|
|
|
static struct bt_sdp_discover_params discov_hfpag = {
|
|
.type = BT_SDP_DISCOVER_SERVICE_SEARCH_ATTR,
|
|
.uuid = BT_UUID_DECLARE_16(BT_SDP_HANDSFREE_AGW_SVCLASS),
|
|
.func = sdp_hfp_ag_user,
|
|
.pool = &sdp_client_pool,
|
|
};
|
|
|
|
static struct bt_sdp_discover_params discov_hfphf = {
|
|
.type = BT_SDP_DISCOVER_SERVICE_SEARCH_ATTR,
|
|
.uuid = BT_UUID_DECLARE_16(BT_SDP_HANDSFREE_SVCLASS),
|
|
.func = sdp_hfp_hf_user,
|
|
.pool = &sdp_client_pool,
|
|
};
|
|
|
|
static struct bt_sdp_discover_params discov_a2src = {
|
|
.type = BT_SDP_DISCOVER_SERVICE_SEARCH_ATTR,
|
|
.uuid = BT_UUID_DECLARE_16(BT_SDP_AUDIO_SOURCE_SVCLASS),
|
|
.func = sdp_a2src_user,
|
|
.pool = &sdp_client_pool,
|
|
};
|
|
|
|
static struct bt_sdp_discover_params discov;
|
|
|
|
static int cmd_sdp_find_record(const struct shell *sh, size_t argc, char *argv[])
|
|
{
|
|
int err;
|
|
const char *action;
|
|
|
|
if (!default_conn) {
|
|
shell_print(sh, "Not connected");
|
|
return -ENOEXEC;
|
|
}
|
|
|
|
action = argv[1];
|
|
|
|
if (!strcmp(action, "HFPAG")) {
|
|
discov = discov_hfpag;
|
|
} else if (!strcmp(action, "HFPHF")) {
|
|
discov = discov_hfphf;
|
|
} else if (!strcmp(action, "A2SRC")) {
|
|
discov = discov_a2src;
|
|
} else {
|
|
shell_help(sh);
|
|
return SHELL_CMD_HELP_PRINTED;
|
|
}
|
|
|
|
shell_print(sh, "SDP UUID \'%s\' gets applied", action);
|
|
|
|
err = bt_sdp_discover(default_conn, &discov);
|
|
if (err) {
|
|
shell_error(sh, "SDP discovery failed: err %d", err);
|
|
return -ENOEXEC;
|
|
}
|
|
|
|
shell_print(sh, "SDP discovery started");
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void bond_info(const struct bt_br_bond_info *info, void *user_data)
|
|
{
|
|
char addr[BT_ADDR_STR_LEN];
|
|
int *bond_count = user_data;
|
|
|
|
bt_addr_to_str(&info->addr, addr, sizeof(addr));
|
|
bt_shell_print("Remote Identity: %s", addr);
|
|
(*bond_count)++;
|
|
}
|
|
|
|
static int cmd_bonds(const struct shell *sh, size_t argc, char *argv[])
|
|
{
|
|
int bond_count = 0;
|
|
|
|
shell_print(sh, "Bonded devices:");
|
|
bt_br_foreach_bond(bond_info, &bond_count);
|
|
shell_print(sh, "Total %d", bond_count);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int cmd_clear(const struct shell *sh, size_t argc, char *argv[])
|
|
{
|
|
bt_addr_t addr;
|
|
int err;
|
|
|
|
if (strcmp(argv[1], "all") == 0) {
|
|
err = bt_br_unpair(NULL);
|
|
if (err) {
|
|
shell_error(sh, "Failed to clear pairings (err %d)", err);
|
|
return err;
|
|
}
|
|
|
|
shell_print(sh, "Pairings successfully cleared");
|
|
return 0;
|
|
}
|
|
|
|
err = bt_addr_from_str(argv[1], &addr);
|
|
if (err) {
|
|
shell_print(sh, "Invalid address");
|
|
return err;
|
|
}
|
|
|
|
err = bt_br_unpair(&addr);
|
|
if (err) {
|
|
shell_error(sh, "Failed to clear pairing (err %d)", err);
|
|
} else {
|
|
shell_print(sh, "Pairing successfully cleared");
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
static int cmd_select(const struct shell *sh, size_t argc, char *argv[])
|
|
{
|
|
char addr_str[BT_ADDR_STR_LEN];
|
|
struct bt_conn *conn;
|
|
bt_addr_t addr;
|
|
int err;
|
|
|
|
err = bt_addr_from_str(argv[1], &addr);
|
|
if (err) {
|
|
shell_error(sh, "Invalid peer address (err %d)", err);
|
|
return err;
|
|
}
|
|
|
|
conn = bt_conn_lookup_addr_br(&addr);
|
|
if (!conn) {
|
|
shell_error(sh, "No matching connection found");
|
|
return -ENOEXEC;
|
|
}
|
|
|
|
if (default_conn != NULL) {
|
|
bt_conn_unref(default_conn);
|
|
}
|
|
|
|
default_conn = conn;
|
|
|
|
bt_addr_to_str(&addr, addr_str, sizeof(addr_str));
|
|
shell_print(sh, "Selected conn is now: %s", addr_str);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const char *get_conn_type_str(uint8_t type)
|
|
{
|
|
switch (type) {
|
|
case BT_CONN_TYPE_LE: return "LE";
|
|
case BT_CONN_TYPE_BR: return "BR/EDR";
|
|
case BT_CONN_TYPE_SCO: return "SCO";
|
|
default: return "Invalid";
|
|
}
|
|
}
|
|
|
|
static const char *get_conn_role_str(uint8_t role)
|
|
{
|
|
switch (role) {
|
|
case BT_CONN_ROLE_CENTRAL: return "central";
|
|
case BT_CONN_ROLE_PERIPHERAL: return "peripheral";
|
|
default: return "Invalid";
|
|
}
|
|
}
|
|
|
|
static int cmd_info(const struct shell *sh, size_t argc, char *argv[])
|
|
{
|
|
struct bt_conn *conn = NULL;
|
|
struct bt_conn_info info;
|
|
bt_addr_t addr;
|
|
int err;
|
|
|
|
if (argc > 1) {
|
|
err = bt_addr_from_str(argv[1], &addr);
|
|
if (err) {
|
|
shell_error(sh, "Invalid peer address (err %d)", err);
|
|
return err;
|
|
}
|
|
conn = bt_conn_lookup_addr_br(&addr);
|
|
} else {
|
|
if (default_conn) {
|
|
conn = bt_conn_ref(default_conn);
|
|
}
|
|
}
|
|
|
|
if (!conn) {
|
|
shell_error(sh, "Not connected");
|
|
return -ENOEXEC;
|
|
}
|
|
|
|
err = bt_conn_get_info(conn, &info);
|
|
if (err) {
|
|
shell_print(sh, "Failed to get info");
|
|
goto done;
|
|
}
|
|
|
|
shell_print(sh, "Type: %s, Role: %s, Id: %u",
|
|
get_conn_type_str(info.type),
|
|
get_conn_role_str(info.role),
|
|
info.id);
|
|
|
|
if (info.type == BT_CONN_TYPE_BR) {
|
|
char addr_str[BT_ADDR_STR_LEN];
|
|
|
|
bt_addr_to_str(info.br.dst, addr_str, sizeof(addr_str));
|
|
shell_print(sh, "Peer address %s", addr_str);
|
|
}
|
|
|
|
done:
|
|
bt_conn_unref(conn);
|
|
|
|
return err;
|
|
}
|
|
|
|
static int cmd_default_handler(const struct shell *sh, size_t argc, char **argv)
|
|
{
|
|
if (argc == 1) {
|
|
shell_help(sh);
|
|
return SHELL_CMD_HELP_PRINTED;
|
|
}
|
|
|
|
shell_error(sh, "%s unknown parameter: %s", argv[0], argv[1]);
|
|
|
|
return -EINVAL;
|
|
}
|
|
|
|
#define HELP_NONE "[none]"
|
|
#define HELP_ADDR "<address: XX:XX:XX:XX:XX:XX>"
|
|
#define HELP_REG \
|
|
"<psm> <mode: none, ret, fc, eret, stream> [hold_credit] " \
|
|
"[mode_optional] [extended_control]"
|
|
|
|
#define HELP_CONN \
|
|
"<psm> <mode: none, ret, fc, eret, stream> [hold_credit] " \
|
|
"[mode_optional] [extended_control]"
|
|
|
|
SHELL_STATIC_SUBCMD_SET_CREATE(echo_cmds,
|
|
SHELL_CMD_ARG(register, NULL, HELP_NONE, cmd_l2cap_echo_reg, 1, 0),
|
|
SHELL_CMD_ARG(unregister, NULL, HELP_NONE, cmd_l2cap_echo_unreg, 1, 0),
|
|
SHELL_CMD_ARG(req, NULL, "<length of data>", cmd_l2cap_echo_req, 2, 0),
|
|
SHELL_CMD_ARG(rsp, NULL, "<identifier> <length of data>", cmd_l2cap_echo_rsp, 3, 0),
|
|
SHELL_SUBCMD_SET_END
|
|
);
|
|
|
|
SHELL_STATIC_SUBCMD_SET_CREATE(l2cap_cmds,
|
|
#if defined(CONFIG_BT_L2CAP_RET_FC)
|
|
SHELL_CMD_ARG(register, NULL, HELP_REG, cmd_l2cap_register, 3, 3),
|
|
SHELL_CMD_ARG(connect, NULL, HELP_CONN, cmd_l2cap_connect, 3, 3),
|
|
#else
|
|
SHELL_CMD_ARG(register, NULL, "<psm>", cmd_l2cap_register, 2, 0),
|
|
SHELL_CMD_ARG(connect, NULL, "<psm>", cmd_l2cap_connect, 2, 0),
|
|
#endif /* CONFIG_BT_L2CAP_RET_FC */
|
|
SHELL_CMD_ARG(disconnect, NULL, HELP_NONE, cmd_l2cap_disconnect, 1, 0),
|
|
SHELL_CMD_ARG(send, NULL, "[number of packets] [length of packet(s)]",
|
|
cmd_l2cap_send, 1, 2),
|
|
#if defined(CONFIG_BT_L2CAP_RET_FC)
|
|
SHELL_CMD_ARG(credits, NULL, HELP_NONE, cmd_l2cap_credits, 1, 0),
|
|
#endif /* CONFIG_BT_L2CAP_RET_FC */
|
|
SHELL_CMD(echo, &echo_cmds, "L2CAP BR ECHO commands", cmd_default_handler),
|
|
SHELL_SUBCMD_SET_END
|
|
);
|
|
|
|
SHELL_STATIC_SUBCMD_SET_CREATE(br_cmds,
|
|
SHELL_CMD_ARG(auth-pincode, NULL, "<pincode>", cmd_auth_pincode, 2, 0),
|
|
SHELL_CMD_ARG(connect, NULL, "<address>", cmd_connect, 2, 0),
|
|
SHELL_CMD_ARG(bonds, NULL, HELP_NONE, cmd_bonds, 1, 0),
|
|
SHELL_CMD_ARG(clear, NULL, "[all] ["HELP_ADDR"]", cmd_clear, 2, 0),
|
|
SHELL_CMD_ARG(select, NULL, HELP_ADDR, cmd_select, 2, 0),
|
|
SHELL_CMD_ARG(info, NULL, HELP_ADDR, cmd_info, 1, 1),
|
|
SHELL_CMD_ARG(discovery, NULL, "<value: on, off> [length: 1-48] [mode: limited]",
|
|
cmd_discovery, 2, 2),
|
|
SHELL_CMD_ARG(iscan, NULL, "<value: on, off> [mode: limited]",
|
|
cmd_discoverable, 2, 1),
|
|
SHELL_CMD(l2cap, &l2cap_cmds, HELP_NONE, cmd_default_handler),
|
|
SHELL_CMD_ARG(oob, NULL, NULL, cmd_oob, 1, 0),
|
|
SHELL_CMD_ARG(pscan, NULL, "<value: on, off>", cmd_connectable, 2, 0),
|
|
SHELL_CMD_ARG(sdp-find, NULL, "<HFPAG, HFPHF>", cmd_sdp_find_record, 2, 0),
|
|
SHELL_SUBCMD_SET_END
|
|
);
|
|
|
|
SHELL_CMD_ARG_REGISTER(br, &br_cmds, "Bluetooth BR/EDR shell commands", cmd_default_handler, 1, 1);
|