2076 lines
43 KiB
C
2076 lines
43 KiB
C
/** @file
|
|
* @brief Bluetooth shell module
|
|
*
|
|
* Provide some Bluetooth shell commands that can be useful to applications.
|
|
*/
|
|
|
|
/*
|
|
* Copyright (c) 2017 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 <misc/printk.h>
|
|
#include <misc/byteorder.h>
|
|
#include <zephyr.h>
|
|
|
|
#include <bluetooth/bluetooth.h>
|
|
#include <bluetooth/conn.h>
|
|
#include <bluetooth/l2cap.h>
|
|
#include <bluetooth/rfcomm.h>
|
|
#include <bluetooth/storage.h>
|
|
#include <bluetooth/sdp.h>
|
|
|
|
#include <shell/shell.h>
|
|
|
|
#include "bt.h"
|
|
#include "gatt.h"
|
|
#include "ll.h"
|
|
|
|
#define DEVICE_NAME CONFIG_BT_DEVICE_NAME
|
|
#define DEVICE_NAME_LEN (sizeof(DEVICE_NAME) - 1)
|
|
#define CREDITS 10
|
|
#define DATA_MTU (23 * CREDITS)
|
|
#define DATA_BREDR_MTU 48
|
|
|
|
#define BT_SHELL_MODULE "bt"
|
|
static bt_addr_le_t id_addr;
|
|
|
|
#if defined(CONFIG_BT_CONN)
|
|
struct bt_conn *default_conn;
|
|
|
|
/* Connection context for BR/EDR legacy pairing in sec mode 3 */
|
|
static struct bt_conn *pairing_conn;
|
|
#endif /* CONFIG_BT_CONN */
|
|
|
|
#if defined(CONFIG_BT_L2CAP_DYNAMIC_CHANNEL)
|
|
NET_BUF_POOL_DEFINE(data_tx_pool, 1, DATA_MTU, BT_BUF_USER_DATA_MIN, NULL);
|
|
NET_BUF_POOL_DEFINE(data_rx_pool, 1, DATA_MTU, BT_BUF_USER_DATA_MIN, NULL);
|
|
#endif
|
|
|
|
#if defined(CONFIG_BT_BREDR)
|
|
NET_BUF_POOL_DEFINE(data_bredr_pool, 1, DATA_BREDR_MTU, BT_BUF_USER_DATA_MIN,
|
|
NULL);
|
|
|
|
#define SDP_CLIENT_USER_BUF_LEN 512
|
|
NET_BUF_POOL_DEFINE(sdp_client_pool, CONFIG_BT_MAX_CONN,
|
|
SDP_CLIENT_USER_BUF_LEN, BT_BUF_USER_DATA_MIN, NULL);
|
|
#endif /* CONFIG_BT_BREDR */
|
|
|
|
#if defined(CONFIG_BT_RFCOMM)
|
|
|
|
static struct bt_sdp_attribute spp_attrs[] = {
|
|
BT_SDP_NEW_SERVICE,
|
|
BT_SDP_LIST(
|
|
BT_SDP_ATTR_SVCLASS_ID_LIST,
|
|
BT_SDP_TYPE_SIZE_VAR(BT_SDP_SEQ8, 3),
|
|
BT_SDP_DATA_ELEM_LIST(
|
|
{
|
|
BT_SDP_TYPE_SIZE(BT_SDP_UUID16),
|
|
BT_SDP_ARRAY_16(BT_SDP_SERIAL_PORT_SVCLASS)
|
|
},
|
|
)
|
|
),
|
|
BT_SDP_LIST(
|
|
BT_SDP_ATTR_PROTO_DESC_LIST,
|
|
BT_SDP_TYPE_SIZE_VAR(BT_SDP_SEQ8, 12),
|
|
BT_SDP_DATA_ELEM_LIST(
|
|
{
|
|
BT_SDP_TYPE_SIZE_VAR(BT_SDP_SEQ8, 3),
|
|
BT_SDP_DATA_ELEM_LIST(
|
|
{
|
|
BT_SDP_TYPE_SIZE(BT_SDP_UUID16),
|
|
BT_SDP_ARRAY_16(BT_UUID_L2CAP_VAL)
|
|
},
|
|
)
|
|
},
|
|
{
|
|
BT_SDP_TYPE_SIZE_VAR(BT_SDP_SEQ8, 5),
|
|
BT_SDP_DATA_ELEM_LIST(
|
|
{
|
|
BT_SDP_TYPE_SIZE(BT_SDP_UUID16),
|
|
BT_SDP_ARRAY_16(BT_UUID_RFCOMM_VAL)
|
|
},
|
|
{
|
|
BT_SDP_TYPE_SIZE(BT_SDP_UINT8),
|
|
BT_SDP_ARRAY_8(BT_RFCOMM_CHAN_SPP)
|
|
},
|
|
)
|
|
},
|
|
)
|
|
),
|
|
BT_SDP_LIST(
|
|
BT_SDP_ATTR_PROFILE_DESC_LIST,
|
|
BT_SDP_TYPE_SIZE_VAR(BT_SDP_SEQ8, 8),
|
|
BT_SDP_DATA_ELEM_LIST(
|
|
{
|
|
BT_SDP_TYPE_SIZE_VAR(BT_SDP_SEQ8, 6),
|
|
BT_SDP_DATA_ELEM_LIST(
|
|
{
|
|
BT_SDP_TYPE_SIZE(BT_SDP_UUID16),
|
|
BT_SDP_ARRAY_16(BT_SDP_SERIAL_PORT_SVCLASS)
|
|
},
|
|
{
|
|
BT_SDP_TYPE_SIZE(BT_SDP_UINT16),
|
|
BT_SDP_ARRAY_16(0x0102)
|
|
},
|
|
)
|
|
},
|
|
)
|
|
),
|
|
BT_SDP_SERVICE_NAME("Serial Port"),
|
|
};
|
|
|
|
static struct bt_sdp_record spp_rec = BT_SDP_RECORD(spp_attrs);
|
|
|
|
#endif /* CONFIG_BT_RFCOMM */
|
|
|
|
static void device_found(const bt_addr_le_t *addr, s8_t rssi, u8_t evtype,
|
|
struct net_buf_simple *buf)
|
|
{
|
|
char le_addr[BT_ADDR_LE_STR_LEN];
|
|
char name[30];
|
|
|
|
memset(name, 0, sizeof(name));
|
|
|
|
while (buf->len > 1) {
|
|
u8_t len, type;
|
|
|
|
len = net_buf_simple_pull_u8(buf);
|
|
if (!len) {
|
|
break;
|
|
}
|
|
|
|
/* Check if field length is correct */
|
|
if (len > buf->len) {
|
|
break;
|
|
}
|
|
|
|
type = net_buf_simple_pull_u8(buf);
|
|
switch (type) {
|
|
case BT_DATA_NAME_SHORTENED:
|
|
case BT_DATA_NAME_COMPLETE:
|
|
if (len > sizeof(name) - 1) {
|
|
memcpy(name, buf->data, sizeof(name) - 1);
|
|
} else {
|
|
memcpy(name, buf->data, len - 1);
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
net_buf_simple_pull(buf, len - 1);
|
|
}
|
|
|
|
bt_addr_le_to_str(addr, le_addr, sizeof(le_addr));
|
|
printk("[DEVICE]: %s, AD evt type %u, RSSI %i %s\n", le_addr, evtype,
|
|
rssi, name);
|
|
}
|
|
|
|
#if !defined(CONFIG_BT_CONN)
|
|
static const char *current_prompt(void)
|
|
{
|
|
return NULL;
|
|
}
|
|
#endif /* !CONFIG_BT_CONN */
|
|
|
|
#if defined(CONFIG_BT_CONN)
|
|
static const char *current_prompt(void)
|
|
{
|
|
static char str[BT_ADDR_LE_STR_LEN + 2];
|
|
static struct bt_conn_info info;
|
|
|
|
if (!default_conn) {
|
|
return NULL;
|
|
}
|
|
|
|
if (bt_conn_get_info(default_conn, &info) < 0) {
|
|
return NULL;
|
|
}
|
|
|
|
if (info.type != BT_CONN_TYPE_LE) {
|
|
return NULL;
|
|
}
|
|
|
|
bt_addr_le_to_str(info.le.dst, str, sizeof(str) - 2);
|
|
strcat(str, "> ");
|
|
return str;
|
|
}
|
|
|
|
static void conn_addr_str(struct bt_conn *conn, char *addr, size_t len)
|
|
{
|
|
struct bt_conn_info info;
|
|
|
|
if (bt_conn_get_info(conn, &info) < 0) {
|
|
addr[0] = '\0';
|
|
return;
|
|
}
|
|
|
|
switch (info.type) {
|
|
#if defined(CONFIG_BT_BREDR)
|
|
case BT_CONN_TYPE_BR:
|
|
bt_addr_to_str(info.br.dst, addr, len);
|
|
break;
|
|
#endif
|
|
case BT_CONN_TYPE_LE:
|
|
bt_addr_le_to_str(info.le.dst, addr, len);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void connected(struct bt_conn *conn, u8_t err)
|
|
{
|
|
char addr[BT_ADDR_LE_STR_LEN];
|
|
|
|
conn_addr_str(conn, addr, sizeof(addr));
|
|
|
|
if (err) {
|
|
printk("Failed to connect to %s (%u)\n", addr, err);
|
|
goto done;
|
|
}
|
|
|
|
printk("Connected: %s\n", addr);
|
|
|
|
if (!default_conn) {
|
|
default_conn = bt_conn_ref(conn);
|
|
}
|
|
|
|
done:
|
|
/* clear connection reference for sec mode 3 pairing */
|
|
if (pairing_conn) {
|
|
bt_conn_unref(pairing_conn);
|
|
pairing_conn = NULL;
|
|
}
|
|
}
|
|
|
|
static void disconnected(struct bt_conn *conn, u8_t reason)
|
|
{
|
|
char addr[BT_ADDR_LE_STR_LEN];
|
|
|
|
conn_addr_str(conn, addr, sizeof(addr));
|
|
printk("Disconnected: %s (reason %u)\n", addr, reason);
|
|
|
|
if (default_conn == conn) {
|
|
bt_conn_unref(default_conn);
|
|
default_conn = NULL;
|
|
}
|
|
}
|
|
|
|
static bool le_param_req(struct bt_conn *conn, struct bt_le_conn_param *param)
|
|
{
|
|
printk("LE conn param req: int (0x%04x, 0x%04x) lat %d to %d\n",
|
|
param->interval_min, param->interval_max, param->latency,
|
|
param->timeout);
|
|
|
|
return true;
|
|
}
|
|
|
|
static void le_param_updated(struct bt_conn *conn, u16_t interval,
|
|
u16_t latency, u16_t timeout)
|
|
{
|
|
printk("LE conn param updated: int 0x%04x lat %d to %d\n", interval,
|
|
latency, timeout);
|
|
}
|
|
|
|
#if defined(CONFIG_BT_BREDR)
|
|
static u8_t sdp_hfp_ag_user(struct bt_conn *conn,
|
|
struct bt_sdp_client_result *result)
|
|
{
|
|
char addr[BT_ADDR_STR_LEN];
|
|
u16_t param, version;
|
|
u16_t features;
|
|
int res;
|
|
|
|
conn_addr_str(conn, addr, sizeof(addr));
|
|
|
|
if (result) {
|
|
printk("SDP HFPAG data@%p (len %u) hint %u from remote %s\n",
|
|
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.
|
|
*/
|
|
res = bt_sdp_get_proto_param(result->resp_buf,
|
|
BT_SDP_PROTO_RFCOMM, ¶m);
|
|
if (res < 0) {
|
|
printk("Error getting Server CN, err %d\n", res);
|
|
goto done;
|
|
}
|
|
printk("HFPAG Server CN param 0x%04x\n", param);
|
|
|
|
res = bt_sdp_get_profile_version(result->resp_buf,
|
|
BT_SDP_HANDSFREE_SVCLASS,
|
|
&version);
|
|
if (res < 0) {
|
|
printk("Error getting profile version, err %d\n", res);
|
|
goto done;
|
|
}
|
|
printk("HFP version param 0x%04x\n", version);
|
|
|
|
/*
|
|
* Focus to get BT_SDP_ATTR_SUPPORTED_FEATURES attribute item to
|
|
* get profile Supported Features mask.
|
|
*/
|
|
res = bt_sdp_get_features(result->resp_buf, &features);
|
|
if (res < 0) {
|
|
printk("Error getting HFPAG Features, err %d\n", res);
|
|
goto done;
|
|
}
|
|
printk("HFPAG Supported Features param 0x%04x\n", features);
|
|
} else {
|
|
printk("No SDP HFPAG data from remote %s\n", addr);
|
|
}
|
|
done:
|
|
return BT_SDP_DISCOVER_UUID_CONTINUE;
|
|
}
|
|
|
|
static u8_t sdp_a2src_user(struct bt_conn *conn,
|
|
struct bt_sdp_client_result *result)
|
|
{
|
|
char addr[BT_ADDR_STR_LEN];
|
|
u16_t param, version;
|
|
u16_t features;
|
|
int res;
|
|
|
|
conn_addr_str(conn, addr, sizeof(addr));
|
|
|
|
if (result) {
|
|
printk("SDP A2SRC data@%p (len %u) hint %u from remote %s\n",
|
|
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.
|
|
*/
|
|
res = bt_sdp_get_proto_param(result->resp_buf,
|
|
BT_SDP_PROTO_L2CAP, ¶m);
|
|
if (res < 0) {
|
|
printk("A2SRC PSM Number not found, err %d\n", res);
|
|
goto done;
|
|
}
|
|
printk("A2SRC Server PSM Number param 0x%04x\n", param);
|
|
|
|
/*
|
|
* Focus to get BT_SDP_ATTR_PROFILE_DESC_LIST attribute item to
|
|
* get profile version number.
|
|
*/
|
|
res = bt_sdp_get_profile_version(result->resp_buf,
|
|
BT_SDP_ADVANCED_AUDIO_SVCLASS,
|
|
&version);
|
|
if (res < 0) {
|
|
printk("A2SRC version not found, err %d\n", res);
|
|
goto done;
|
|
}
|
|
printk("A2SRC version param 0x%04x\n", version);
|
|
|
|
/*
|
|
* Focus to get BT_SDP_ATTR_SUPPORTED_FEATURES attribute item to
|
|
* get profile supported features mask.
|
|
*/
|
|
res = bt_sdp_get_features(result->resp_buf, &features);
|
|
if (res < 0) {
|
|
printk("A2SRC Features not found, err %d\n", res);
|
|
goto done;
|
|
}
|
|
printk("A2SRC Supported Features param 0x%04x\n", features);
|
|
} else {
|
|
printk("No SDP A2SRC data from remote %s\n", addr);
|
|
}
|
|
done:
|
|
return BT_SDP_DISCOVER_UUID_CONTINUE;
|
|
}
|
|
|
|
static struct bt_sdp_discover_params discov_hfpag = {
|
|
.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_a2src = {
|
|
.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;
|
|
#endif
|
|
|
|
#if defined(CONFIG_BT_SMP)
|
|
static void identity_resolved(struct bt_conn *conn, const bt_addr_le_t *rpa,
|
|
const bt_addr_le_t *identity)
|
|
{
|
|
char addr_identity[BT_ADDR_LE_STR_LEN];
|
|
char addr_rpa[BT_ADDR_LE_STR_LEN];
|
|
|
|
bt_addr_le_to_str(identity, addr_identity, sizeof(addr_identity));
|
|
bt_addr_le_to_str(rpa, addr_rpa, sizeof(addr_rpa));
|
|
|
|
printk("Identity resolved %s -> %s\n", addr_rpa, addr_identity);
|
|
}
|
|
#endif
|
|
|
|
#if defined(CONFIG_BT_SMP) || defined(CONFIG_BT_BREDR)
|
|
static void security_changed(struct bt_conn *conn, bt_security_t level)
|
|
{
|
|
char addr[BT_ADDR_LE_STR_LEN];
|
|
|
|
conn_addr_str(conn, addr, sizeof(addr));
|
|
printk("Security changed: %s level %u\n", addr, level);
|
|
}
|
|
#endif
|
|
|
|
static struct bt_conn_cb conn_callbacks = {
|
|
.connected = connected,
|
|
.disconnected = disconnected,
|
|
.le_param_req = le_param_req,
|
|
.le_param_updated = le_param_updated,
|
|
#if defined(CONFIG_BT_SMP)
|
|
.identity_resolved = identity_resolved,
|
|
#endif
|
|
#if defined(CONFIG_BT_SMP) || defined(CONFIG_BT_BREDR)
|
|
.security_changed = security_changed,
|
|
#endif
|
|
};
|
|
#endif /* CONFIG_BT_CONN */
|
|
|
|
static int char2hex(const char *c, u8_t *x)
|
|
{
|
|
if (*c >= '0' && *c <= '9') {
|
|
*x = *c - '0';
|
|
} else if (*c >= 'a' && *c <= 'f') {
|
|
*x = *c - 'a' + 10;
|
|
} else if (*c >= 'A' && *c <= 'F') {
|
|
*x = *c - 'A' + 10;
|
|
} else {
|
|
return -EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int str2bt_addr(const char *str, bt_addr_t *addr)
|
|
{
|
|
int i, j;
|
|
u8_t tmp;
|
|
|
|
if (strlen(str) != 17) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
for (i = 5, j = 1; *str != '\0'; str++, j++) {
|
|
if (!(j % 3) && (*str != ':')) {
|
|
return -EINVAL;
|
|
} else if (*str == ':') {
|
|
i--;
|
|
continue;
|
|
}
|
|
|
|
addr->val[i] = addr->val[i] << 4;
|
|
|
|
if (char2hex(str, &tmp) < 0) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
addr->val[i] |= tmp;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int str2bt_addr_le(const char *str, const char *type, bt_addr_le_t *addr)
|
|
{
|
|
int err;
|
|
|
|
err = str2bt_addr(str, &addr->a);
|
|
if (err < 0) {
|
|
return err;
|
|
}
|
|
|
|
if (!strcmp(type, "public") || !strcmp(type, "(public)")) {
|
|
addr->type = BT_ADDR_LE_PUBLIC;
|
|
} else if (!strcmp(type, "random") || !strcmp(type, "(random)")) {
|
|
addr->type = BT_ADDR_LE_RANDOM;
|
|
} else {
|
|
return -EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static ssize_t storage_read(const bt_addr_le_t *addr, u16_t key, void *data,
|
|
size_t length)
|
|
{
|
|
if (addr) {
|
|
return -ENOENT;
|
|
}
|
|
|
|
if (key == BT_STORAGE_ID_ADDR && length == sizeof(id_addr) &&
|
|
bt_addr_le_cmp(&id_addr, BT_ADDR_LE_ANY)) {
|
|
bt_addr_le_copy(data, &id_addr);
|
|
return sizeof(id_addr);
|
|
}
|
|
|
|
return -EIO;
|
|
}
|
|
|
|
static ssize_t storage_write(const bt_addr_le_t *addr, u16_t key,
|
|
const void *data, size_t length)
|
|
{
|
|
if (addr) {
|
|
return -ENOENT;
|
|
}
|
|
|
|
if (key == BT_STORAGE_ID_ADDR && length == sizeof(id_addr)) {
|
|
bt_addr_le_copy(&id_addr, data);
|
|
return sizeof(id_addr);
|
|
}
|
|
|
|
return -EIO;
|
|
}
|
|
|
|
static int storage_clear(const bt_addr_le_t *addr)
|
|
{
|
|
return -ENOSYS;
|
|
}
|
|
|
|
static void bt_ready(int err)
|
|
{
|
|
if (err) {
|
|
printk("Bluetooth init failed (err %d)\n", err);
|
|
return;
|
|
}
|
|
|
|
printk("Bluetooth initialized\n");
|
|
|
|
#if defined(CONFIG_BT_CONN)
|
|
default_conn = NULL;
|
|
|
|
bt_conn_cb_register(&conn_callbacks);
|
|
#endif /* CONFIG_BT_CONN */
|
|
}
|
|
|
|
static int cmd_init(int argc, char *argv[])
|
|
{
|
|
static const struct bt_storage storage = {
|
|
.read = storage_read,
|
|
.write = storage_write,
|
|
.clear = storage_clear,
|
|
};
|
|
int err;
|
|
|
|
if (argc > 1) {
|
|
if (argc < 3) {
|
|
printk("Invalid address\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
err = str2bt_addr_le(argv[1], argv[2], &id_addr);
|
|
if (err) {
|
|
printk("Invalid address (err %d)\n", err);
|
|
bt_addr_le_cmp(&id_addr, BT_ADDR_LE_ANY);
|
|
return -EINVAL;
|
|
}
|
|
|
|
bt_storage_register(&storage);
|
|
}
|
|
|
|
err = bt_enable(bt_ready);
|
|
if (err) {
|
|
printk("Bluetooth init failed (err %d)\n", err);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void cmd_active_scan_on(int dups)
|
|
{
|
|
int err;
|
|
struct bt_le_scan_param param = {
|
|
.type = BT_HCI_LE_SCAN_ACTIVE,
|
|
.filter_dup = BT_HCI_LE_SCAN_FILTER_DUP_ENABLE,
|
|
.interval = BT_GAP_SCAN_FAST_INTERVAL,
|
|
.window = BT_GAP_SCAN_FAST_WINDOW };
|
|
|
|
if (dups >= 0) {
|
|
param.filter_dup = dups;
|
|
}
|
|
|
|
err = bt_le_scan_start(¶m, device_found);
|
|
if (err) {
|
|
printk("Bluetooth set active scan failed (err %d)\n", err);
|
|
} else {
|
|
printk("Bluetooth active scan enabled\n");
|
|
}
|
|
}
|
|
|
|
static void cmd_passive_scan_on(int dups)
|
|
{
|
|
struct bt_le_scan_param param = {
|
|
.type = BT_HCI_LE_SCAN_PASSIVE,
|
|
.filter_dup = BT_HCI_LE_SCAN_FILTER_DUP_DISABLE,
|
|
.interval = 0x10,
|
|
.window = 0x10 };
|
|
int err;
|
|
|
|
if (dups >= 0) {
|
|
param.filter_dup = dups;
|
|
}
|
|
|
|
err = bt_le_scan_start(¶m, device_found);
|
|
if (err) {
|
|
printk("Bluetooth set passive scan failed (err %d)\n", err);
|
|
} else {
|
|
printk("Bluetooth passive scan enabled\n");
|
|
}
|
|
}
|
|
|
|
static void cmd_scan_off(void)
|
|
{
|
|
int err;
|
|
|
|
err = bt_le_scan_stop();
|
|
if (err) {
|
|
printk("Stopping scanning failed (err %d)\n", err);
|
|
} else {
|
|
printk("Scan successfully stopped\n");
|
|
}
|
|
}
|
|
|
|
static int cmd_scan(int argc, char *argv[])
|
|
{
|
|
const char *action;
|
|
int dups = -1;
|
|
|
|
if (argc < 2) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* Parse duplicate filtering data */
|
|
if (argc >= 3) {
|
|
const char *dup_filter = argv[2];
|
|
|
|
if (!strcmp(dup_filter, "dups")) {
|
|
dups = BT_HCI_LE_SCAN_FILTER_DUP_DISABLE;
|
|
} else if (!strcmp(dup_filter, "nodups")) {
|
|
dups = BT_HCI_LE_SCAN_FILTER_DUP_ENABLE;
|
|
} else {
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
|
|
action = argv[1];
|
|
if (!strcmp(action, "on")) {
|
|
cmd_active_scan_on(dups);
|
|
} else if (!strcmp(action, "off")) {
|
|
cmd_scan_off();
|
|
} else if (!strcmp(action, "passive")) {
|
|
cmd_passive_scan_on(dups);
|
|
} else {
|
|
return -EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct bt_data ad_discov[] = {
|
|
BT_DATA_BYTES(BT_DATA_FLAGS, (BT_LE_AD_GENERAL | BT_LE_AD_NO_BREDR)),
|
|
};
|
|
|
|
static const struct bt_data sd[] = {
|
|
BT_DATA(BT_DATA_NAME_COMPLETE, DEVICE_NAME, DEVICE_NAME_LEN),
|
|
};
|
|
|
|
static int cmd_advertise(int argc, char *argv[])
|
|
{
|
|
struct bt_le_adv_param param;
|
|
const struct bt_data *ad, *scan_rsp;
|
|
size_t ad_len, scan_rsp_len;
|
|
int err;
|
|
|
|
if (argc < 2) {
|
|
goto fail;
|
|
}
|
|
|
|
if (!strcmp(argv[1], "off")) {
|
|
if (bt_le_adv_stop() < 0) {
|
|
printk("Failed to stop advertising\n");
|
|
} else {
|
|
printk("Advertising stopped\n");
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
param.own_addr = NULL;
|
|
param.interval_min = BT_GAP_ADV_FAST_INT_MIN_2;
|
|
param.interval_max = BT_GAP_ADV_FAST_INT_MAX_2;
|
|
|
|
if (!strcmp(argv[1], "on")) {
|
|
param.options = BT_LE_ADV_OPT_CONNECTABLE;
|
|
scan_rsp = sd;
|
|
scan_rsp_len = ARRAY_SIZE(sd);
|
|
} else if (!strcmp(argv[1], "scan")) {
|
|
param.options = 0;
|
|
scan_rsp = sd;
|
|
scan_rsp_len = ARRAY_SIZE(sd);
|
|
} else if (!strcmp(argv[1], "nconn")) {
|
|
param.options = 0;
|
|
scan_rsp = NULL;
|
|
scan_rsp_len = 0;
|
|
} else {
|
|
goto fail;
|
|
}
|
|
|
|
/* Parse advertisement data */
|
|
if (argc >= 3) {
|
|
const char *mode = argv[2];
|
|
|
|
if (!strcmp(mode, "discov")) {
|
|
ad = ad_discov;
|
|
ad_len = ARRAY_SIZE(ad_discov);
|
|
} else if (!strcmp(mode, "non_discov")) {
|
|
ad = NULL;
|
|
ad_len = 0;
|
|
} else {
|
|
goto fail;
|
|
}
|
|
} else {
|
|
ad = ad_discov;
|
|
ad_len = ARRAY_SIZE(ad_discov);
|
|
}
|
|
|
|
err = bt_le_adv_start(¶m, ad, ad_len, scan_rsp, scan_rsp_len);
|
|
if (err < 0) {
|
|
printk("Failed to start advertising (err %d)\n", err);
|
|
} else {
|
|
printk("Advertising started\n");
|
|
}
|
|
|
|
return 0;
|
|
|
|
fail:
|
|
return -EINVAL;
|
|
}
|
|
|
|
#if defined(CONFIG_BT_CONN)
|
|
static int cmd_connect_le(int argc, char *argv[])
|
|
{
|
|
int err;
|
|
bt_addr_le_t addr;
|
|
struct bt_conn *conn;
|
|
|
|
if (argc < 3) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
err = str2bt_addr_le(argv[1], argv[2], &addr);
|
|
if (err) {
|
|
printk("Invalid peer address (err %d)\n", err);
|
|
return 0;
|
|
}
|
|
|
|
conn = bt_conn_create_le(&addr, BT_LE_CONN_PARAM_DEFAULT);
|
|
|
|
if (!conn) {
|
|
printk("Connection failed\n");
|
|
} else {
|
|
|
|
printk("Connection pending\n");
|
|
|
|
/* unref connection obj in advance as app user */
|
|
bt_conn_unref(conn);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int cmd_disconnect(int argc, char *argv[])
|
|
{
|
|
struct bt_conn *conn;
|
|
int err;
|
|
|
|
if (default_conn && argc < 3) {
|
|
conn = bt_conn_ref(default_conn);
|
|
} else {
|
|
bt_addr_le_t addr;
|
|
|
|
if (argc < 3) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
err = str2bt_addr_le(argv[1], argv[2], &addr);
|
|
if (err) {
|
|
printk("Invalid peer address (err %d)\n", err);
|
|
return 0;
|
|
}
|
|
|
|
conn = bt_conn_lookup_addr_le(&addr);
|
|
}
|
|
|
|
if (!conn) {
|
|
printk("Not connected\n");
|
|
return 0;
|
|
}
|
|
|
|
err = bt_conn_disconnect(conn, BT_HCI_ERR_REMOTE_USER_TERM_CONN);
|
|
if (err) {
|
|
printk("Disconnection failed (err %d)\n", err);
|
|
}
|
|
|
|
bt_conn_unref(conn);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int cmd_auto_conn(int argc, char *argv[])
|
|
{
|
|
bt_addr_le_t addr;
|
|
int err;
|
|
|
|
if (argc < 3) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
err = str2bt_addr_le(argv[1], argv[2], &addr);
|
|
if (err) {
|
|
printk("Invalid peer address (err %d)\n", err);
|
|
return 0;
|
|
}
|
|
|
|
if (argc < 4) {
|
|
bt_le_set_auto_conn(&addr, BT_LE_CONN_PARAM_DEFAULT);
|
|
} else if (!strcmp(argv[3], "on")) {
|
|
bt_le_set_auto_conn(&addr, BT_LE_CONN_PARAM_DEFAULT);
|
|
} else if (!strcmp(argv[3], "off")) {
|
|
bt_le_set_auto_conn(&addr, NULL);
|
|
} else {
|
|
return -EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int cmd_select(int argc, char *argv[])
|
|
{
|
|
struct bt_conn *conn;
|
|
bt_addr_le_t addr;
|
|
int err;
|
|
|
|
if (argc < 3) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
err = str2bt_addr_le(argv[1], argv[2], &addr);
|
|
if (err) {
|
|
printk("Invalid peer address (err %d)\n", err);
|
|
return 0;
|
|
}
|
|
|
|
conn = bt_conn_lookup_addr_le(&addr);
|
|
if (!conn) {
|
|
printk("No matching connection found\n");
|
|
return 0;
|
|
}
|
|
|
|
if (default_conn) {
|
|
bt_conn_unref(default_conn);
|
|
}
|
|
|
|
default_conn = conn;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int cmd_conn_update(int argc, char *argv[])
|
|
{
|
|
struct bt_le_conn_param param;
|
|
int err;
|
|
|
|
if (argc < 5) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
param.interval_min = strtoul(argv[1], NULL, 16);
|
|
param.interval_max = strtoul(argv[2], NULL, 16);
|
|
param.latency = strtoul(argv[3], NULL, 16);
|
|
param.timeout = strtoul(argv[4], NULL, 16);
|
|
|
|
err = bt_conn_le_param_update(default_conn, ¶m);
|
|
if (err) {
|
|
printk("conn update failed (err %d).\n", err);
|
|
} else {
|
|
printk("conn update initiated.\n");
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int cmd_oob(int argc, char *argv[])
|
|
{
|
|
char addr[BT_ADDR_LE_STR_LEN];
|
|
struct bt_le_oob oob;
|
|
int err;
|
|
|
|
err = bt_le_oob_get_local(&oob);
|
|
if (err) {
|
|
printk("OOB data failed\n");
|
|
return 0;
|
|
}
|
|
|
|
bt_addr_le_to_str(&oob.addr, addr, sizeof(addr));
|
|
|
|
printk("OOB data:\n");
|
|
printk(" addr %s\n", addr);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int cmd_clear(int argc, char *argv[])
|
|
{
|
|
bt_addr_le_t addr;
|
|
int err;
|
|
|
|
if (argc < 2) {
|
|
printk("Specify remote address or \"all\"\n");
|
|
return 0;
|
|
}
|
|
|
|
if (strcmp(argv[1], "all") == 0) {
|
|
err = bt_storage_clear(NULL);
|
|
if (err) {
|
|
printk("Failed to clear storage (err %d)\n", err);
|
|
} else {
|
|
printk("Storage successfully cleared\n");
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
if (argc < 3) {
|
|
#if defined(CONFIG_BT_BREDR)
|
|
addr.type = BT_ADDR_LE_PUBLIC;
|
|
err = str2bt_addr(argv[1], &addr.a);
|
|
#else
|
|
printk("Both address and address type needed\n");
|
|
return 0;
|
|
#endif
|
|
} else {
|
|
err = str2bt_addr_le(argv[1], argv[2], &addr);
|
|
}
|
|
|
|
if (err) {
|
|
printk("Invalid address\n");
|
|
return 0;
|
|
}
|
|
|
|
err = bt_storage_clear(&addr);
|
|
if (err) {
|
|
printk("Failed to clear storage (err %d)\n", err);
|
|
} else {
|
|
printk("Storage successfully cleared\n");
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
#endif /* CONFIG_BT_CONN */
|
|
|
|
#if defined(CONFIG_BT_SMP) || defined(CONFIG_BT_BREDR)
|
|
static int cmd_security(int argc, char *argv[])
|
|
{
|
|
int err, sec;
|
|
|
|
if (!default_conn) {
|
|
printk("Not connected\n");
|
|
return 0;
|
|
}
|
|
|
|
if (argc < 2) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
sec = *argv[1] - '0';
|
|
|
|
err = bt_conn_security(default_conn, sec);
|
|
if (err) {
|
|
printk("Setting security failed (err %d)\n", err);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void auth_passkey_display(struct bt_conn *conn, unsigned int passkey)
|
|
{
|
|
char addr[BT_ADDR_LE_STR_LEN];
|
|
char passkey_str[7];
|
|
|
|
bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr));
|
|
|
|
snprintk(passkey_str, 7, "%06u", passkey);
|
|
|
|
printk("Passkey for %s: %s\n", addr, passkey_str);
|
|
}
|
|
|
|
static void auth_passkey_confirm(struct bt_conn *conn, unsigned int passkey)
|
|
{
|
|
char addr[BT_ADDR_LE_STR_LEN];
|
|
char passkey_str[7];
|
|
|
|
bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr));
|
|
|
|
snprintk(passkey_str, 7, "%06u", passkey);
|
|
|
|
printk("Confirm passkey for %s: %s\n", addr, passkey_str);
|
|
}
|
|
|
|
static void auth_passkey_entry(struct bt_conn *conn)
|
|
{
|
|
char addr[BT_ADDR_LE_STR_LEN];
|
|
|
|
bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr));
|
|
|
|
printk("Enter passkey for %s\n", addr);
|
|
}
|
|
|
|
static void auth_cancel(struct bt_conn *conn)
|
|
{
|
|
char addr[BT_ADDR_LE_STR_LEN];
|
|
|
|
conn_addr_str(conn, addr, sizeof(addr));
|
|
|
|
printk("Pairing cancelled: %s\n", addr);
|
|
|
|
/* clear connection reference for sec mode 3 pairing */
|
|
if (pairing_conn) {
|
|
bt_conn_unref(pairing_conn);
|
|
pairing_conn = NULL;
|
|
}
|
|
}
|
|
|
|
static void auth_pairing_confirm(struct bt_conn *conn)
|
|
{
|
|
char addr[BT_ADDR_LE_STR_LEN];
|
|
|
|
bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr));
|
|
|
|
printk("Confirm pairing for %s\n", addr);
|
|
}
|
|
|
|
#if defined(CONFIG_BT_BREDR)
|
|
static void auth_pincode_entry(struct bt_conn *conn, bool highsec)
|
|
{
|
|
char addr[BT_ADDR_STR_LEN];
|
|
struct bt_conn_info info;
|
|
|
|
if (bt_conn_get_info(conn, &info) < 0) {
|
|
return;
|
|
}
|
|
|
|
if (info.type != BT_CONN_TYPE_BR) {
|
|
return;
|
|
}
|
|
|
|
bt_addr_to_str(info.br.dst, addr, sizeof(addr));
|
|
|
|
if (highsec) {
|
|
printk("Enter 16 digits wide PIN code for %s\n", addr);
|
|
} else {
|
|
printk("Enter PIN code for %s\n", addr);
|
|
}
|
|
|
|
/*
|
|
* Save connection info since in security mode 3 (link level enforced
|
|
* security) PIN request callback is called before connected callback
|
|
*/
|
|
if (!default_conn && !pairing_conn) {
|
|
pairing_conn = bt_conn_ref(conn);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
static struct bt_conn_auth_cb auth_cb_display = {
|
|
.passkey_display = auth_passkey_display,
|
|
.passkey_entry = NULL,
|
|
.passkey_confirm = NULL,
|
|
#if defined(CONFIG_BT_BREDR)
|
|
.pincode_entry = auth_pincode_entry,
|
|
#endif
|
|
.cancel = auth_cancel,
|
|
.pairing_confirm = auth_pairing_confirm,
|
|
};
|
|
|
|
static struct bt_conn_auth_cb auth_cb_display_yes_no = {
|
|
.passkey_display = auth_passkey_display,
|
|
.passkey_entry = NULL,
|
|
.passkey_confirm = auth_passkey_confirm,
|
|
#if defined(CONFIG_BT_BREDR)
|
|
.pincode_entry = auth_pincode_entry,
|
|
#endif
|
|
.cancel = auth_cancel,
|
|
.pairing_confirm = auth_pairing_confirm,
|
|
};
|
|
|
|
static struct bt_conn_auth_cb auth_cb_input = {
|
|
.passkey_display = NULL,
|
|
.passkey_entry = auth_passkey_entry,
|
|
.passkey_confirm = NULL,
|
|
#if defined(CONFIG_BT_BREDR)
|
|
.pincode_entry = auth_pincode_entry,
|
|
#endif
|
|
.cancel = auth_cancel,
|
|
.pairing_confirm = auth_pairing_confirm,
|
|
};
|
|
|
|
static struct bt_conn_auth_cb auth_cb_all = {
|
|
.passkey_display = auth_passkey_display,
|
|
.passkey_entry = auth_passkey_entry,
|
|
.passkey_confirm = auth_passkey_confirm,
|
|
#if defined(CONFIG_BT_BREDR)
|
|
.pincode_entry = auth_pincode_entry,
|
|
#endif
|
|
.cancel = auth_cancel,
|
|
.pairing_confirm = auth_pairing_confirm,
|
|
};
|
|
|
|
static int cmd_auth(int argc, char *argv[])
|
|
{
|
|
if (argc < 2) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (!strcmp(argv[1], "all")) {
|
|
bt_conn_auth_cb_register(&auth_cb_all);
|
|
} else if (!strcmp(argv[1], "input")) {
|
|
bt_conn_auth_cb_register(&auth_cb_input);
|
|
} else if (!strcmp(argv[1], "display")) {
|
|
bt_conn_auth_cb_register(&auth_cb_display);
|
|
} else if (!strcmp(argv[1], "yesno")) {
|
|
bt_conn_auth_cb_register(&auth_cb_display_yes_no);
|
|
} else if (!strcmp(argv[1], "none")) {
|
|
bt_conn_auth_cb_register(NULL);
|
|
} else {
|
|
return -EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int cmd_auth_cancel(int argc, char *argv[])
|
|
{
|
|
struct bt_conn *conn;
|
|
|
|
if (default_conn) {
|
|
conn = default_conn;
|
|
} else if (pairing_conn) {
|
|
conn = pairing_conn;
|
|
} else {
|
|
conn = NULL;
|
|
}
|
|
|
|
if (!conn) {
|
|
printk("Not connected\n");
|
|
return 0;
|
|
}
|
|
|
|
bt_conn_auth_cancel(conn);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int cmd_auth_passkey_confirm(int argc, char *argv[])
|
|
{
|
|
if (!default_conn) {
|
|
printk("Not connected\n");
|
|
return 0;
|
|
}
|
|
|
|
bt_conn_auth_passkey_confirm(default_conn);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int cmd_auth_pairing_confirm(int argc, char *argv[])
|
|
{
|
|
if (!default_conn) {
|
|
printk("Not connected\n");
|
|
return 0;
|
|
}
|
|
|
|
bt_conn_auth_pairing_confirm(default_conn);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int cmd_auth_passkey(int argc, char *argv[])
|
|
{
|
|
unsigned int passkey;
|
|
|
|
if (!default_conn) {
|
|
printk("Not connected\n");
|
|
return 0;
|
|
}
|
|
|
|
if (argc < 2) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
passkey = atoi(argv[1]);
|
|
if (passkey > 999999) {
|
|
printk("Passkey should be between 0-999999\n");
|
|
return 0;
|
|
}
|
|
|
|
bt_conn_auth_passkey_entry(default_conn, passkey);
|
|
|
|
return 0;
|
|
}
|
|
#endif /* CONFIG_BT_SMP) || CONFIG_BT_BREDR */
|
|
|
|
#if defined(CONFIG_BT_BREDR)
|
|
static int cmd_auth_pincode(int argc, char *argv[])
|
|
{
|
|
struct bt_conn *conn;
|
|
u8_t max = 16;
|
|
|
|
if (default_conn) {
|
|
conn = default_conn;
|
|
} else if (pairing_conn) {
|
|
conn = pairing_conn;
|
|
} else {
|
|
conn = NULL;
|
|
}
|
|
|
|
if (!conn) {
|
|
printk("Not connected\n");
|
|
return 0;
|
|
}
|
|
|
|
if (argc < 2) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (strlen(argv[1]) > max) {
|
|
printk("PIN code value invalid - enter max %u digits\n", max);
|
|
return 0;
|
|
}
|
|
|
|
printk("PIN code \"%s\" applied\n", argv[1]);
|
|
|
|
bt_conn_auth_pincode_entry(conn, argv[1]);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int cmd_connect_bredr(int argc, char *argv[])
|
|
{
|
|
struct bt_conn *conn;
|
|
bt_addr_t addr;
|
|
int err;
|
|
|
|
if (argc < 2) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
err = str2bt_addr(argv[1], &addr);
|
|
if (err) {
|
|
printk("Invalid peer address (err %d)\n", err);
|
|
return 0;
|
|
}
|
|
|
|
conn = bt_conn_create_br(&addr, BT_BR_CONN_PARAM_DEFAULT);
|
|
if (!conn) {
|
|
printk("Connection failed\n");
|
|
} else {
|
|
|
|
printk("Connection pending\n");
|
|
|
|
/* unref connection obj in advance as app user */
|
|
bt_conn_unref(conn);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void br_device_found(const bt_addr_t *addr, s8_t rssi,
|
|
const u8_t cod[3], const u8_t eir[240])
|
|
{
|
|
char br_addr[BT_ADDR_STR_LEN];
|
|
char name[239];
|
|
int len = 240;
|
|
|
|
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));
|
|
|
|
printk("[DEVICE]: %s, RSSI %i %s\n", br_addr, rssi, name);
|
|
}
|
|
|
|
static struct bt_br_discovery_result br_discovery_results[5];
|
|
|
|
static void br_discovery_complete(struct bt_br_discovery_result *results,
|
|
size_t count)
|
|
{
|
|
size_t i;
|
|
|
|
printk("BR/EDR discovery complete\n");
|
|
|
|
for (i = 0; i < count; i++) {
|
|
br_device_found(&results[i].addr, results[i].rssi,
|
|
results[i].cod, results[i].eir);
|
|
}
|
|
}
|
|
|
|
static int cmd_bredr_discovery(int argc, char *argv[])
|
|
{
|
|
const char *action;
|
|
|
|
if (argc < 2) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
action = argv[1];
|
|
if (!strcmp(action, "on")) {
|
|
struct bt_br_discovery_param param;
|
|
|
|
param.limited = false;
|
|
param.length = 8;
|
|
|
|
if (argc > 2) {
|
|
param.length = atoi(argv[2]);
|
|
}
|
|
|
|
if (argc > 3 && !strcmp(argv[3], "limited")) {
|
|
param.limited = true;
|
|
}
|
|
|
|
if (bt_br_discovery_start(¶m, br_discovery_results,
|
|
ARRAY_SIZE(br_discovery_results),
|
|
br_discovery_complete) < 0) {
|
|
printk("Failed to start discovery\n");
|
|
return 0;
|
|
}
|
|
|
|
printk("Discovery started\n");
|
|
} else if (!strcmp(action, "off")) {
|
|
if (bt_br_discovery_stop()) {
|
|
printk("Failed to stop discovery\n");
|
|
return 0;
|
|
}
|
|
|
|
printk("Discovery stopped\n");
|
|
} else {
|
|
return -EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
#endif /* CONFIG_BT_BREDR */
|
|
|
|
#if defined(CONFIG_BT_L2CAP_DYNAMIC_CHANNEL)
|
|
static void hexdump(const u8_t *data, size_t len)
|
|
{
|
|
int n = 0;
|
|
|
|
while (len--) {
|
|
if (n % 16 == 0) {
|
|
printk("%08X ", n);
|
|
}
|
|
|
|
printk("%02X ", *data++);
|
|
|
|
n++;
|
|
if (n % 8 == 0) {
|
|
if (n % 16 == 0) {
|
|
printk("\n");
|
|
} else {
|
|
printk(" ");
|
|
}
|
|
}
|
|
}
|
|
|
|
if (n % 16) {
|
|
printk("\n");
|
|
}
|
|
}
|
|
|
|
static u32_t l2cap_rate;
|
|
|
|
static void l2cap_recv_metrics(struct bt_l2cap_chan *chan, struct net_buf *buf)
|
|
{
|
|
static u32_t len;
|
|
static u32_t cycle_stamp;
|
|
u32_t delta;
|
|
|
|
delta = k_cycle_get_32() - cycle_stamp;
|
|
delta = SYS_CLOCK_HW_CYCLES_TO_NS(delta);
|
|
|
|
/* if last data rx-ed was greater than 1 second in the past,
|
|
* reset the metrics.
|
|
*/
|
|
if (delta > 1000000000) {
|
|
len = 0;
|
|
l2cap_rate = 0;
|
|
cycle_stamp = k_cycle_get_32();
|
|
} else {
|
|
len += buf->len;
|
|
l2cap_rate = ((u64_t)len << 3) * 1000000000 / delta;
|
|
}
|
|
}
|
|
|
|
static void l2cap_recv(struct bt_l2cap_chan *chan, struct net_buf *buf)
|
|
{
|
|
printk("Incoming data channel %p len %u\n", chan, buf->len);
|
|
|
|
if (buf->len) {
|
|
hexdump(buf->data, buf->len);
|
|
}
|
|
}
|
|
|
|
static void l2cap_connected(struct bt_l2cap_chan *chan)
|
|
{
|
|
printk("Channel %p connected\n", chan);
|
|
}
|
|
|
|
static void l2cap_disconnected(struct bt_l2cap_chan *chan)
|
|
{
|
|
printk("Channel %p disconnected\n", chan);
|
|
}
|
|
|
|
static struct net_buf *l2cap_alloc_buf(struct bt_l2cap_chan *chan)
|
|
{
|
|
/* print if metrics is disabled */
|
|
if (chan->ops->recv != l2cap_recv_metrics) {
|
|
printk("Channel %p requires buffer\n", chan);
|
|
}
|
|
|
|
return net_buf_alloc(&data_rx_pool, K_FOREVER);
|
|
}
|
|
|
|
static struct bt_l2cap_chan_ops l2cap_ops = {
|
|
.alloc_buf = l2cap_alloc_buf,
|
|
.recv = l2cap_recv,
|
|
.connected = l2cap_connected,
|
|
.disconnected = l2cap_disconnected,
|
|
};
|
|
|
|
static struct bt_l2cap_le_chan l2cap_chan = {
|
|
.chan.ops = &l2cap_ops,
|
|
.rx.mtu = DATA_MTU,
|
|
};
|
|
|
|
static int l2cap_accept(struct bt_conn *conn, struct bt_l2cap_chan **chan)
|
|
{
|
|
printk("Incoming conn %p\n", conn);
|
|
|
|
if (l2cap_chan.chan.conn) {
|
|
printk("No channels available\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
*chan = &l2cap_chan.chan;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static struct bt_l2cap_server server = {
|
|
.accept = l2cap_accept,
|
|
};
|
|
|
|
static int cmd_l2cap_register(int argc, char *argv[])
|
|
{
|
|
if (argc < 2) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (server.psm) {
|
|
printk("Already registered\n");
|
|
return 0;
|
|
}
|
|
|
|
server.psm = strtoul(argv[1], NULL, 16);
|
|
|
|
if (argc > 2) {
|
|
server.sec_level = strtoul(argv[2], NULL, 10);
|
|
}
|
|
|
|
if (bt_l2cap_server_register(&server) < 0) {
|
|
printk("Unable to register psm\n");
|
|
server.psm = 0;
|
|
} else {
|
|
printk("L2CAP psm %u sec_level %u registered\n", server.psm,
|
|
server.sec_level);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int cmd_l2cap_connect(int argc, char *argv[])
|
|
{
|
|
u16_t psm;
|
|
int err;
|
|
|
|
if (!default_conn) {
|
|
printk("Not connected\n");
|
|
return 0;
|
|
}
|
|
|
|
if (argc < 2) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (l2cap_chan.chan.conn) {
|
|
printk("Channel already in use\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
psm = strtoul(argv[1], NULL, 16);
|
|
|
|
err = bt_l2cap_chan_connect(default_conn, &l2cap_chan.chan, psm);
|
|
if (err < 0) {
|
|
printk("Unable to connect to psm %u (err %u)\n", psm, err);
|
|
} else {
|
|
printk("L2CAP connection pending\n");
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int cmd_l2cap_disconnect(int argc, char *argv[])
|
|
{
|
|
int err;
|
|
|
|
err = bt_l2cap_chan_disconnect(&l2cap_chan.chan);
|
|
if (err) {
|
|
printk("Unable to disconnect: %u\n", -err);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int cmd_l2cap_send(int argc, char *argv[])
|
|
{
|
|
static u8_t buf_data[DATA_MTU] = { [0 ... (DATA_MTU - 1)] = 0xff };
|
|
int ret, len, count = 1;
|
|
struct net_buf *buf;
|
|
|
|
if (argc > 1) {
|
|
count = strtoul(argv[1], NULL, 10);
|
|
}
|
|
|
|
len = min(l2cap_chan.tx.mtu, DATA_MTU - BT_L2CAP_CHAN_SEND_RESERVE);
|
|
|
|
while (count--) {
|
|
buf = net_buf_alloc(&data_tx_pool, K_FOREVER);
|
|
net_buf_reserve(buf, BT_L2CAP_CHAN_SEND_RESERVE);
|
|
|
|
net_buf_add_mem(buf, buf_data, len);
|
|
ret = bt_l2cap_chan_send(&l2cap_chan.chan, buf);
|
|
if (ret < 0) {
|
|
printk("Unable to send: %d\n", -ret);
|
|
net_buf_unref(buf);
|
|
break;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int cmd_l2cap_metrics(int argc, char *argv[])
|
|
{
|
|
const char *action;
|
|
|
|
if (argc < 2) {
|
|
printk("l2cap rate: %u bps.\n", l2cap_rate);
|
|
|
|
return 0;
|
|
}
|
|
|
|
action = argv[1];
|
|
|
|
if (!strcmp(action, "on")) {
|
|
l2cap_ops.recv = l2cap_recv_metrics;
|
|
} else if (!strcmp(action, "off")) {
|
|
l2cap_ops.recv = l2cap_recv;
|
|
} else {
|
|
return -EINVAL;
|
|
}
|
|
|
|
printk("l2cap metrics %s.\n", action);
|
|
|
|
return 0;
|
|
}
|
|
|
|
#endif
|
|
|
|
#if defined(CONFIG_BT_BREDR)
|
|
static void l2cap_bredr_recv(struct bt_l2cap_chan *chan, struct net_buf *buf)
|
|
{
|
|
printk("Incoming data channel %p len %u\n", chan, buf->len);
|
|
}
|
|
|
|
static void l2cap_bredr_connected(struct bt_l2cap_chan *chan)
|
|
{
|
|
printk("Channel %p connected\n", chan);
|
|
}
|
|
|
|
static void l2cap_bredr_disconnected(struct bt_l2cap_chan *chan)
|
|
{
|
|
printk("Channel %p disconnected\n", chan);
|
|
}
|
|
|
|
static struct net_buf *l2cap_bredr_alloc_buf(struct bt_l2cap_chan *chan)
|
|
{
|
|
printk("Channel %p requires buffer\n", chan);
|
|
|
|
return net_buf_alloc(&data_bredr_pool, K_FOREVER);
|
|
}
|
|
|
|
static struct bt_l2cap_chan_ops l2cap_bredr_ops = {
|
|
.alloc_buf = l2cap_bredr_alloc_buf,
|
|
.recv = l2cap_bredr_recv,
|
|
.connected = l2cap_bredr_connected,
|
|
.disconnected = l2cap_bredr_disconnected,
|
|
};
|
|
|
|
static struct bt_l2cap_br_chan l2cap_bredr_chan = {
|
|
.chan.ops = &l2cap_bredr_ops,
|
|
/* Set for now min. MTU */
|
|
.rx.mtu = 48,
|
|
};
|
|
|
|
static int l2cap_bredr_accept(struct bt_conn *conn, struct bt_l2cap_chan **chan)
|
|
{
|
|
printk("Incoming BR/EDR conn %p\n", conn);
|
|
|
|
if (l2cap_bredr_chan.chan.conn) {
|
|
printk("No channels available");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
*chan = &l2cap_bredr_chan.chan;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static struct bt_l2cap_server br_server = {
|
|
.accept = l2cap_bredr_accept,
|
|
};
|
|
|
|
static int cmd_bredr_l2cap_register(int argc, char *argv[])
|
|
{
|
|
if (argc < 2) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (br_server.psm) {
|
|
printk("Already registered\n");
|
|
return 0;
|
|
}
|
|
|
|
br_server.psm = strtoul(argv[1], NULL, 16);
|
|
|
|
if (bt_l2cap_br_server_register(&br_server) < 0) {
|
|
printk("Unable to register psm\n");
|
|
br_server.psm = 0;
|
|
} else {
|
|
printk("L2CAP psm %u registered\n", br_server.psm);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
#if defined(CONFIG_BT_RFCOMM)
|
|
static void rfcomm_bredr_recv(struct bt_rfcomm_dlc *dlci, struct net_buf *buf)
|
|
{
|
|
printk("Incoming data dlc %p len %u\n", dlci, buf->len);
|
|
}
|
|
|
|
static void rfcomm_bredr_connected(struct bt_rfcomm_dlc *dlci)
|
|
{
|
|
printk("Dlc %p connected\n", dlci);
|
|
}
|
|
|
|
static void rfcomm_bredr_disconnected(struct bt_rfcomm_dlc *dlci)
|
|
{
|
|
printk("Dlc %p disconnected\n", dlci);
|
|
}
|
|
|
|
static struct bt_rfcomm_dlc_ops rfcomm_bredr_ops = {
|
|
.recv = rfcomm_bredr_recv,
|
|
.connected = rfcomm_bredr_connected,
|
|
.disconnected = rfcomm_bredr_disconnected,
|
|
};
|
|
|
|
static struct bt_rfcomm_dlc rfcomm_dlc = {
|
|
.ops = &rfcomm_bredr_ops,
|
|
.mtu = 30,
|
|
};
|
|
|
|
static int rfcomm_bredr_accept(struct bt_conn *conn, struct bt_rfcomm_dlc **dlc)
|
|
{
|
|
printk("Incoming RFCOMM conn %p\n", conn);
|
|
|
|
if (rfcomm_dlc.session) {
|
|
printk("No channels available");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
*dlc = &rfcomm_dlc;
|
|
|
|
return 0;
|
|
}
|
|
|
|
struct bt_rfcomm_server rfcomm_server = {
|
|
.accept = &rfcomm_bredr_accept,
|
|
};
|
|
|
|
static int cmd_bredr_rfcomm_register(int argc, char *argv[])
|
|
{
|
|
int ret;
|
|
|
|
if (rfcomm_server.channel) {
|
|
printk("Already registered\n");
|
|
return 0;
|
|
}
|
|
|
|
rfcomm_server.channel = BT_RFCOMM_CHAN_SPP;
|
|
|
|
ret = bt_rfcomm_server_register(&rfcomm_server);
|
|
if (ret < 0) {
|
|
printk("Unable to register channel %x\n", ret);
|
|
rfcomm_server.channel = 0;
|
|
} else {
|
|
printk("RFCOMM channel %u registered\n", rfcomm_server.channel);
|
|
bt_sdp_register_service(&spp_rec);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int cmd_rfcomm_connect(int argc, char *argv[])
|
|
{
|
|
u8_t channel;
|
|
int err;
|
|
|
|
if (!default_conn) {
|
|
printk("Not connected\n");
|
|
return 0;
|
|
}
|
|
|
|
if (argc < 2) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
channel = strtoul(argv[1], NULL, 16);
|
|
|
|
err = bt_rfcomm_dlc_connect(default_conn, &rfcomm_dlc, channel);
|
|
if (err < 0) {
|
|
printk("Unable to connect to channel %d (err %u)\n",
|
|
channel, err);
|
|
} else {
|
|
printk("RFCOMM connection pending\n");
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int cmd_rfcomm_send(int argc, char *argv[])
|
|
{
|
|
u8_t buf_data[DATA_BREDR_MTU] = { [0 ... (DATA_BREDR_MTU - 1)] =
|
|
0xff };
|
|
int ret, len, count = 1;
|
|
struct net_buf *buf;
|
|
|
|
if (argc > 1) {
|
|
count = strtoul(argv[1], NULL, 10);
|
|
}
|
|
|
|
while (count--) {
|
|
buf = bt_rfcomm_create_pdu(&data_bredr_pool);
|
|
/* Should reserve one byte in tail for FCS */
|
|
len = min(rfcomm_dlc.mtu, net_buf_tailroom(buf) - 1);
|
|
|
|
net_buf_add_mem(buf, buf_data, len);
|
|
ret = bt_rfcomm_dlc_send(&rfcomm_dlc, buf);
|
|
if (ret < 0) {
|
|
printk("Unable to send: %d\n", -ret);
|
|
net_buf_unref(buf);
|
|
break;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int cmd_rfcomm_disconnect(int argc, char *argv[])
|
|
{
|
|
int err;
|
|
|
|
err = bt_rfcomm_dlc_disconnect(&rfcomm_dlc);
|
|
if (err) {
|
|
printk("Unable to disconnect: %u\n", -err);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
#endif /* CONFIG_BT_RFCOMM) */
|
|
|
|
static int cmd_bredr_discoverable(int argc, char *argv[])
|
|
{
|
|
int err;
|
|
const char *action;
|
|
|
|
if (argc < 2) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
action = argv[1];
|
|
|
|
if (!strcmp(action, "on")) {
|
|
err = bt_br_set_discoverable(true);
|
|
} else if (!strcmp(action, "off")) {
|
|
err = bt_br_set_discoverable(false);
|
|
} else {
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (err) {
|
|
printk("BR/EDR set/reset discoverable failed (err %d)\n", err);
|
|
} else {
|
|
printk("BR/EDR set/reset discoverable done\n");
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int cmd_bredr_connectable(int argc, char *argv[])
|
|
{
|
|
int err;
|
|
const char *action;
|
|
|
|
if (argc < 2) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
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 {
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (err) {
|
|
printk("BR/EDR set/rest connectable failed (err %d)\n", err);
|
|
} else {
|
|
printk("BR/EDR set/reset connectable done\n");
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int cmd_bredr_oob(int 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) {
|
|
printk("BR/EDR OOB data failed\n");
|
|
return 0;
|
|
}
|
|
|
|
bt_addr_to_str(&oob.addr, addr, sizeof(addr));
|
|
|
|
printk("BR/EDR OOB data:\n");
|
|
printk(" addr %s\n", addr);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int cmd_bredr_sdp_find_record(int argc, char *argv[])
|
|
{
|
|
int err = 0, res;
|
|
const char *action;
|
|
|
|
if (!default_conn) {
|
|
printk("Not connected\n");
|
|
return 0;
|
|
}
|
|
|
|
if (argc < 2) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
action = argv[1];
|
|
|
|
if (!strcmp(action, "HFPAG")) {
|
|
discov = discov_hfpag;
|
|
} else if (!strcmp(action, "A2SRC")) {
|
|
discov = discov_a2src;
|
|
} else {
|
|
err = -EINVAL;
|
|
}
|
|
|
|
if (err) {
|
|
printk("SDP UUID to resolve not valid (err %d)\n", err);
|
|
printk("Supported UUID are \'HFPAG\' \'A2SRC\' only\n");
|
|
return err;
|
|
}
|
|
|
|
printk("SDP UUID \'%s\' gets applied\n", action);
|
|
|
|
res = bt_sdp_discover(default_conn, &discov);
|
|
if (res) {
|
|
printk("SDP discovery failed: result %d\n", res);
|
|
} else {
|
|
printk("SDP discovery started\n");
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
#define HELP_NONE "[none]"
|
|
#define HELP_ADDR_LE "<address: XX:XX:XX:XX:XX:XX> <type: (public|random)>"
|
|
|
|
static const struct shell_cmd bt_commands[] = {
|
|
{ "init", cmd_init, HELP_ADDR_LE },
|
|
{ "scan", cmd_scan,
|
|
"<value: on, passive, off> <dup filter: dups, nodups>" },
|
|
{ "advertise", cmd_advertise,
|
|
"<type: off, on, scan, nconn> <mode: discov, non_discov>" },
|
|
#if defined(CONFIG_BT_CONN)
|
|
{ "connect", cmd_connect_le, HELP_ADDR_LE },
|
|
{ "disconnect", cmd_disconnect, HELP_NONE },
|
|
{ "auto-conn", cmd_auto_conn, HELP_ADDR_LE },
|
|
{ "select", cmd_select, HELP_ADDR_LE },
|
|
{ "conn-update", cmd_conn_update, "<min> <max> <latency> <timeout>" },
|
|
{ "oob", cmd_oob },
|
|
{ "clear", cmd_clear },
|
|
#if defined(CONFIG_BT_SMP) || defined(CONFIG_BT_BREDR)
|
|
{ "security", cmd_security, "<security level: 0, 1, 2, 3>" },
|
|
{ "auth", cmd_auth,
|
|
"<authentication method: all, input, display, yesno, none>" },
|
|
{ "auth-cancel", cmd_auth_cancel, HELP_NONE },
|
|
{ "auth-passkey", cmd_auth_passkey, "<passkey>" },
|
|
{ "auth-passkey-confirm", cmd_auth_passkey_confirm, HELP_NONE },
|
|
{ "auth-pairing-confirm", cmd_auth_pairing_confirm, HELP_NONE },
|
|
#if defined(CONFIG_BT_BREDR)
|
|
{ "auth-pincode", cmd_auth_pincode, "<pincode>" },
|
|
#endif /* CONFIG_BT_BREDR */
|
|
#endif /* CONFIG_BT_SMP || CONFIG_BT_BREDR) */
|
|
#if defined(CONFIG_BT_GATT_CLIENT)
|
|
{ "gatt-exchange-mtu", cmd_gatt_exchange_mtu, HELP_NONE },
|
|
{ "gatt-discover-primary", cmd_gatt_discover,
|
|
"<UUID> [start handle] [end handle]" },
|
|
{ "gatt-discover-secondary", cmd_gatt_discover,
|
|
"<UUID> [start handle] [end handle]" },
|
|
{ "gatt-discover-include", cmd_gatt_discover,
|
|
"[UUID] [start handle] [end handle]" },
|
|
{ "gatt-discover-characteristic", cmd_gatt_discover,
|
|
"[UUID] [start handle] [end handle]" },
|
|
{ "gatt-discover-descriptor", cmd_gatt_discover,
|
|
"[UUID] [start handle] [end handle]" },
|
|
{ "gatt-read", cmd_gatt_read, "<handle> [offset]" },
|
|
{ "gatt-read-multiple", cmd_gatt_mread, "<handle 1> <handle 2> ..." },
|
|
{ "gatt-write", cmd_gatt_write, "<handle> <offset> <data> [length]" },
|
|
{ "gatt-write-without-response", cmd_gatt_write_without_rsp,
|
|
"<handle> <data> [length] [repeat]" },
|
|
{ "gatt-write-signed", cmd_gatt_write_without_rsp,
|
|
"<handle> <data> [length] [repeat]" },
|
|
{ "gatt-subscribe", cmd_gatt_subscribe,
|
|
"<CCC handle> <value handle> [ind]" },
|
|
{ "gatt-unsubscribe", cmd_gatt_unsubscribe, HELP_NONE },
|
|
#endif /* CONFIG_BT_GATT_CLIENT */
|
|
{ "gatt-show-db", cmd_gatt_show_db, HELP_NONE },
|
|
{ "gatt-register-service", cmd_gatt_register_test_svc,
|
|
"register pre-predefined test service" },
|
|
{ "gatt-unregister-service", cmd_gatt_unregister_test_svc,
|
|
"unregister pre-predefined test service" },
|
|
{ "gatt-metrics", cmd_gatt_write_cmd_metrics,
|
|
"register vendr char and measure rx [value on, off]" },
|
|
#if defined(CONFIG_BT_L2CAP_DYNAMIC_CHANNEL)
|
|
{ "l2cap-register", cmd_l2cap_register, "<psm> [sec_level]" },
|
|
{ "l2cap-connect", cmd_l2cap_connect, "<psm>" },
|
|
{ "l2cap-disconnect", cmd_l2cap_disconnect, HELP_NONE },
|
|
{ "l2cap-send", cmd_l2cap_send, "<number of packets>" },
|
|
{ "l2cap-metrics", cmd_l2cap_metrics, "<value on, off>" },
|
|
#endif
|
|
#if defined(CONFIG_BT_BREDR)
|
|
{ "br-iscan", cmd_bredr_discoverable, "<value: on, off>" },
|
|
{ "br-pscan", cmd_bredr_connectable, "value: on, off" },
|
|
{ "br-connect", cmd_connect_bredr, "<address>" },
|
|
{ "br-discovery", cmd_bredr_discovery,
|
|
"<value: on, off> [length: 1-48] [mode: limited]" },
|
|
{ "br-l2cap-register", cmd_bredr_l2cap_register, "<psm>" },
|
|
{ "br-oob", cmd_bredr_oob },
|
|
{ "br-sdp-find", cmd_bredr_sdp_find_record, "<HFPAG>" },
|
|
#if defined(CONFIG_BT_RFCOMM)
|
|
{ "br-rfcomm-register", cmd_bredr_rfcomm_register },
|
|
{ "br-rfcomm-connect", cmd_rfcomm_connect, "<channel>" },
|
|
{ "br-rfcomm-send", cmd_rfcomm_send, "<number of packets>"},
|
|
{ "br-rfcomm-disconnect", cmd_rfcomm_disconnect, HELP_NONE },
|
|
#endif /* CONFIG_BT_RFCOMM */
|
|
#endif /* CONFIG_BT_BREDR */
|
|
#endif /* CONFIG_BT_CONN */
|
|
#if defined(CONFIG_BT_CTLR_ADV_EXT)
|
|
{ "advx", cmd_advx, "<on off> [coded] [anon] [txp]" },
|
|
{ "scanx", cmd_scanx, "<on passive off> [coded]" },
|
|
#endif /* CONFIG_BT_CTLR_ADV_EXT */
|
|
#if defined(CONFIG_BT_CTLR_DTM)
|
|
{ "test_tx", cmd_test_tx, "<chan> <len> <type> <phy>" },
|
|
{ "test_rx", cmd_test_rx, "<chan> <phy> <mod_idx>" },
|
|
{ "test_end", cmd_test_end, HELP_NONE},
|
|
#endif /* CONFIG_BT_CTLR_ADV_EXT */
|
|
{ NULL, NULL, NULL }
|
|
};
|
|
|
|
SHELL_REGISTER_WITH_PROMPT(BT_SHELL_MODULE, bt_commands, current_prompt);
|