/** @file
* @brief Bluetooth shell module
*
* Provide some Bluetooth shell commands that can be useful to applications.
*/
/*
* Copyright (c) 2017 Intel Corporation
* Copyright (c) 2018 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <errno.h>
#include <zephyr/types.h>
#include <ctype.h>
#include <stddef.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <zephyr/sys/printk.h>
#include <zephyr/sys/byteorder.h>
#include <zephyr/sys/util.h>
#include <zephyr/zephyr.h>
#include <zephyr/settings/settings.h>
#include <zephyr/bluetooth/hci.h>
#include <zephyr/bluetooth/bluetooth.h>
#include <zephyr/bluetooth/conn.h>
#include <zephyr/bluetooth/rfcomm.h>
#include <zephyr/bluetooth/sdp.h>
#include <zephyr/bluetooth/iso.h>
#include <zephyr/shell/shell.h>
#include "bt.h"
#include "ll.h"
#include "hci.h"
static bool no_settings_load;
uint8_t selected_id = BT_ID_DEFAULT;
const struct shell *ctx_shell;
#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;
static struct bt_le_oob oob_local;
#if defined(CONFIG_BT_SMP) || defined(CONFIG_BT_BREDR)
static struct bt_le_oob oob_remote;
#endif /* CONFIG_BT_SMP || CONFIG_BT_BREDR) */
#endif /* CONFIG_BT_CONN */
#if defined(CONFIG_BT_SMP)
static struct bt_conn_auth_info_cb auth_info_cb;
#endif /* CONFIG_BT_SMP */
#define NAME_LEN 30
#define KEY_STR_LEN 33
/*
* Based on the maximum number of parameters for HCI_LE_Generate_DHKey
* See BT Core Spec V5.2 Vol. 4, Part E, section 7.8.37
*/
#define HCI_CMD_MAX_PARAM 65
#if defined(CONFIG_BT_EXT_ADV)
uint8_t selected_adv;
struct bt_le_ext_adv *adv_sets[CONFIG_BT_EXT_ADV_MAX_ADV_SET];
#endif /* CONFIG_BT_EXT_ADV */
#if defined(CONFIG_BT_OBSERVER) || defined(CONFIG_BT_USER_PHY_UPDATE)
static const char *phy2str(uint8_t phy)
{
switch (phy) {
case 0: return "No packets";
case BT_GAP_LE_PHY_1M: return "LE 1M";
case BT_GAP_LE_PHY_2M: return "LE 2M";
case BT_GAP_LE_PHY_CODED: return "LE Coded";
default: return "Unknown";
}
}
#endif
#if defined(CONFIG_BT_OBSERVER)
static struct bt_scan_filter {
char name[NAME_LEN];
bool name_set;
char addr[18]; /* fits xx:xx:xx:xx:xx:xx\0 */
bool addr_set;
} scan_filter;
/**
* @brief Compares two strings without case sensitivy
*
* @param substr The substring
* @param str The string to find the substring in
*
* @return true if @substr is a substring of @p, else false
*/
static bool is_substring(const char *substr, const char *str)
{
const size_t str_len = strlen(str);
const size_t sub_str_len = strlen(substr);
if (sub_str_len > str_len) {
return false;
}
for (size_t pos = 0; pos < str_len; pos++) {
if (tolower(substr[0]) == tolower(str[pos])) {
if (pos + sub_str_len > str_len) {
shell_print(ctx_shell, "length fail");
return false;
}
if (strncasecmp(substr, &str[pos], sub_str_len) == 0) {
return true;
}
}
}
return false;
}
static bool data_cb(struct bt_data *data, void *user_data)
{
char *name = user_data;
switch (data->type) {
case BT_DATA_NAME_SHORTENED:
case BT_DATA_NAME_COMPLETE:
memcpy(name, data->data, MIN(data->data_len, NAME_LEN - 1));
return false;
default:
return true;
}
}
static void scan_recv(const struct bt_le_scan_recv_info *info,
struct net_buf_simple *buf)
{
char le_addr[BT_ADDR_LE_STR_LEN];
char name[NAME_LEN];
(void)memset(name, 0, sizeof(name));
bt_data_parse(buf, data_cb, name);
bt_addr_le_to_str(info->addr, le_addr, sizeof(le_addr));
if (scan_filter.name_set && !is_substring(scan_filter.name, name)) {
return;
}
if (scan_filter.addr_set && !is_substring(scan_filter.addr, le_addr)) {
return;
}
shell_print(ctx_shell, "[DEVICE]: %s, AD evt type %u, RSSI %i %s "
"C:%u S:%u D:%d SR:%u E:%u Prim: %s, Secn: %s, "
"Interval: 0x%04x (%u ms), SID: 0x%x",
le_addr, info->adv_type, info->rssi, name,
(info->adv_props & BT_GAP_ADV_PROP_CONNECTABLE) != 0,
(info->adv_props & BT_GAP_ADV_PROP_SCANNABLE) != 0,
(info->adv_props & BT_GAP_ADV_PROP_DIRECTED) != 0,
(info->adv_props & BT_GAP_ADV_PROP_SCAN_RESPONSE) != 0,
(info->adv_props & BT_GAP_ADV_PROP_EXT_ADV) != 0,
phy2str(info->primary_phy), phy2str(info->secondary_phy),
info->interval, BT_CONN_INTERVAL_TO_MS(info->interval),
info->sid);
}
static void scan_timeout(void)
{
shell_print(ctx_shell, "Scan timeout");
}
#endif /* CONFIG_BT_OBSERVER */
#if defined(CONFIG_BT_EXT_ADV)
#if defined(CONFIG_BT_BROADCASTER)
static void adv_sent(struct bt_le_ext_adv *adv,
struct bt_le_ext_adv_sent_info *info)
{
shell_print(ctx_shell, "Advertiser[%d] %p sent %d",
bt_le_ext_adv_get_index(adv), adv, info->num_sent);
}
static void adv_scanned(struct bt_le_ext_adv *adv,
struct bt_le_ext_adv_scanned_info *info)
{
char str[BT_ADDR_LE_STR_LEN];
bt_addr_le_to_str(info->addr, str, sizeof(str));
shell_print(ctx_shell, "Advertiser[%d] %p scanned by %s",
bt_le_ext_adv_get_index(adv), adv, str);
}
#endif /* CONFIG_BT_BROADCASTER */
#if defined(CONFIG_BT_PERIPHERAL)
static void adv_connected(struct bt_le_ext_adv *adv,
struct bt_le_ext_adv_connected_info *info)
{
char str[BT_ADDR_LE_STR_LEN];
bt_addr_le_to_str(bt_conn_get_dst(info->conn), str, sizeof(str));
shell_print(ctx_shell, "Advertiser[%d] %p connected by %s",
bt_le_ext_adv_get_index(adv), adv, str);
}
#endif /* CONFIG_BT_PERIPHERAL */
#endif /* CONFIG_BT_EXT_ADV */
#if !defined(CONFIG_BT_CONN)
#if 0 /* FIXME: Add support for changing prompt */
static const char *current_prompt(void)
{
return NULL;
}
#endif
#endif /* !CONFIG_BT_CONN */
#if defined(CONFIG_BT_CONN)
#if 0 /* FIXME: Add support for changing prompt */
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;
}
#endif
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 print_le_oob(const struct shell *sh, struct bt_le_oob *oob)
{
char addr[BT_ADDR_LE_STR_LEN];
char c[KEY_STR_LEN];
char r[KEY_STR_LEN];
bt_addr_le_to_str(&oob->addr, addr, sizeof(addr));
bin2hex(oob->le_sc_data.c, sizeof(oob->le_sc_data.c), c, sizeof(c));
bin2hex(oob->le_sc_data.r, sizeof(oob->le_sc_data.r), r, sizeof(r));
shell_print(sh, "OOB data:");
shell_print(sh, "%-29s %-32s %-32s", "addr", "random", "confirm");
shell_print(sh, "%29s %32s %32s", addr, r, c);
}
static void connected(struct bt_conn *conn, uint8_t err)
{
char addr[BT_ADDR_LE_STR_LEN];
conn_addr_str(conn, addr, sizeof(addr));
if (err) {
shell_error(ctx_shell, "Failed to connect to %s (0x%02x)", addr,
err);
goto done;
}
shell_print(ctx_shell, "Connected: %s", 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, uint8_t reason)
{
char addr[BT_ADDR_LE_STR_LEN];
conn_addr_str(conn, addr, sizeof(addr));
shell_print(ctx_shell, "Disconnected: %s (reason 0x%02x)", 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)
{
shell_print(ctx_shell, "LE conn param req: int (0x%04x, 0x%04x) lat %d"
" to %d", param->interval_min, param->interval_max,
param->latency, param->timeout);
return true;
}
static void le_param_updated(struct bt_conn *conn, uint16_t interval,
uint16_t latency, uint16_t timeout)
{
shell_print(ctx_shell, "LE conn param updated: int 0x%04x lat %d "
"to %d", interval, latency, timeout);
}
#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));
shell_print(ctx_shell, "Identity resolved %s -> %s", addr_rpa,
addr_identity);
}
#endif
#if defined(CONFIG_BT_SMP) || defined(CONFIG_BT_BREDR)
static const char *security_err_str(enum bt_security_err err)
{
switch (err) {
case BT_SECURITY_ERR_SUCCESS:
return "Success";
case BT_SECURITY_ERR_AUTH_FAIL:
return "Authentication failure";
case BT_SECURITY_ERR_PIN_OR_KEY_MISSING:
return "PIN or key missing";
case BT_SECURITY_ERR_OOB_NOT_AVAILABLE:
return "OOB not available";
case BT_SECURITY_ERR_AUTH_REQUIREMENT:
return "Authentication requirements";
case BT_SECURITY_ERR_PAIR_NOT_SUPPORTED:
return "Pairing not supported";
case BT_SECURITY_ERR_PAIR_NOT_ALLOWED:
return "Pairing not allowed";
case BT_SECURITY_ERR_INVALID_PARAM:
return "Invalid parameters";
case BT_SECURITY_ERR_UNSPECIFIED:
return "Unspecified";
default:
return "Unknown";
}
}
static void security_changed(struct bt_conn *conn, bt_security_t level,
enum bt_security_err err)
{
char addr[BT_ADDR_LE_STR_LEN];
conn_addr_str(conn, addr, sizeof(addr));
if (!err) {
shell_print(ctx_shell, "Security changed: %s level %u", addr,
level);
} else {
shell_print(ctx_shell, "Security failed: %s level %u "
"reason: %s (%d)",
addr, level, security_err_str(err), err);
}
}
#endif
#if defined(CONFIG_BT_REMOTE_INFO)
static const char *ver_str(uint8_t ver)
{
const char * const str[] = {
"1.0b", "1.1", "1.2", "2.0", "2.1", "3.0", "4.0", "4.1", "4.2",
"5.0", "5.1", "5.2", "5.3"
};
if (ver < ARRAY_SIZE(str)) {
return str[ver];
}
return "unknown";
}
static void remote_info_available(struct bt_conn *conn,
struct bt_conn_remote_info *remote_info)
{
struct bt_conn_info info;
bt_conn_get_info(conn, &info);
if (IS_ENABLED(CONFIG_BT_REMOTE_VERSION)) {
shell_print(ctx_shell,
"Remote LMP version %s (0x%02x) subversion 0x%04x "
"manufacturer 0x%04x", ver_str(remote_info->version),
remote_info->version, remote_info->subversion,
remote_info->manufacturer);
}
if (info.type == BT_CONN_TYPE_LE) {
uint8_t features[8];
char features_str[2 * sizeof(features) + 1];
sys_memcpy_swap(features, remote_info->le.features,
sizeof(features));
bin2hex(features, sizeof(features),
features_str, sizeof(features_str));
shell_print(ctx_shell, "LE Features: 0x%s ", features_str);
}
}
#endif /* defined(CONFIG_BT_REMOTE_INFO) */
#if defined(CONFIG_BT_USER_DATA_LEN_UPDATE)
void le_data_len_updated(struct bt_conn *conn,
struct bt_conn_le_data_len_info *info)
{
shell_print(ctx_shell,
"LE data len updated: TX (len: %d time: %d)"
" RX (len: %d time: %d)", info->tx_max_len,
info->tx_max_time, info->rx_max_len, info->rx_max_time);
}
#endif
#if defined(CONFIG_BT_USER_PHY_UPDATE)
void le_phy_updated(struct bt_conn *conn,
struct bt_conn_le_phy_info *info)
{
shell_print(ctx_shell, "LE PHY updated: TX PHY %s, RX PHY %s",
phy2str(info->tx_phy), phy2str(info->rx_phy));
}
#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
#if defined(CONFIG_BT_REMOTE_INFO)
.remote_info_available = remote_info_available,
#endif
#if defined(CONFIG_BT_USER_DATA_LEN_UPDATE)
.le_data_len_updated = le_data_len_updated,
#endif
#if defined(CONFIG_BT_USER_PHY_UPDATE)
.le_phy_updated = le_phy_updated,
#endif
};
#endif /* CONFIG_BT_CONN */
#if defined(CONFIG_BT_OBSERVER)
static struct bt_le_scan_cb scan_callbacks = {
.recv = scan_recv,
.timeout = scan_timeout,
};
#endif /* defined(CONFIG_BT_OBSERVER) */
#if defined(CONFIG_BT_EXT_ADV)
#if defined(CONFIG_BT_BROADCASTER)
static struct bt_le_ext_adv_cb adv_callbacks = {
.sent = adv_sent,
.scanned = adv_scanned,
#if defined(CONFIG_BT_PERIPHERAL)
.connected = adv_connected,
#endif /* CONFIG_BT_PERIPHERAL */
};
#endif /* CONFIG_BT_BROADCASTER */
#endif /* CONFIG_BT_EXT_ADV */
#if defined(CONFIG_BT_PER_ADV_SYNC)
struct bt_le_per_adv_sync *per_adv_syncs[CONFIG_BT_PER_ADV_SYNC_MAX];
static void per_adv_sync_sync_cb(struct bt_le_per_adv_sync *sync,
struct bt_le_per_adv_sync_synced_info *info)
{
char le_addr[BT_ADDR_LE_STR_LEN];
char past_peer[BT_ADDR_LE_STR_LEN];
bt_addr_le_to_str(info->addr, le_addr, sizeof(le_addr));
if (info->conn) {
conn_addr_str(info->conn, past_peer, sizeof(past_peer));
} else {
memset(past_peer, 0, sizeof(past_peer));
}
shell_print(ctx_shell, "PER_ADV_SYNC[%u]: [DEVICE]: %s synced, "
"Interval 0x%04x (%u ms), PHY %s, SD 0x%04X, PAST peer %s",
bt_le_per_adv_sync_get_index(sync), le_addr,
info->interval, BT_CONN_INTERVAL_TO_MS(info->interval),
phy2str(info->phy), info->service_data, past_peer);
if (info->conn) { /* if from PAST */
for (int i = 0; i < ARRAY_SIZE(per_adv_syncs); i++) {
if (!per_adv_syncs[i]) {
per_adv_syncs[i] = sync;
break;
}
}
}
}
static void per_adv_sync_terminated_cb(
struct bt_le_per_adv_sync *sync,
const struct bt_le_per_adv_sync_term_info *info)
{
char le_addr[BT_ADDR_LE_STR_LEN];
for (int i = 0; i < ARRAY_SIZE(per_adv_syncs); i++) {
if (per_adv_syncs[i] == sync) {
per_adv_syncs[i] = NULL;
break;
}
}
bt_addr_le_to_str(info->addr, le_addr, sizeof(le_addr));
shell_print(ctx_shell, "PER_ADV_SYNC[%u]: [DEVICE]: %s sync terminated",
bt_le_per_adv_sync_get_index(sync), le_addr);
}
static void per_adv_sync_recv_cb(
struct bt_le_per_adv_sync *sync,
const struct bt_le_per_adv_sync_recv_info *info,
struct net_buf_simple *buf)
{
char le_addr[BT_ADDR_LE_STR_LEN];
bt_addr_le_to_str(info->addr, le_addr, sizeof(le_addr));
shell_print(ctx_shell, "PER_ADV_SYNC[%u]: [DEVICE]: %s, tx_power %i, "
"RSSI %i, CTE %u, data length %u",
bt_le_per_adv_sync_get_index(sync), le_addr, info->tx_power,
info->rssi, info->cte_type, buf->len);
}
static void per_adv_sync_biginfo_cb(struct bt_le_per_adv_sync *sync,
const struct bt_iso_biginfo *biginfo)
{
char le_addr[BT_ADDR_LE_STR_LEN];
bt_addr_le_to_str(biginfo->addr, le_addr, sizeof(le_addr));
shell_print(ctx_shell, "BIG_INFO PER_ADV_SYNC[%u]: [DEVICE]: %s, sid 0x%02x, num_bis %u, "
"nse 0x%02x, interval 0x%04x (%u ms), bn 0x%02x, pto 0x%02x, irc 0x%02x, "
"max_pdu 0x%04x, sdu_interval 0x%04x, max_sdu 0x%04x, phy %s, framing 0x%02x, "
"%sencrypted",
bt_le_per_adv_sync_get_index(sync), le_addr, biginfo->sid, biginfo->num_bis,
biginfo->sub_evt_count, biginfo->iso_interval,
BT_CONN_INTERVAL_TO_MS(biginfo->iso_interval), biginfo->burst_number,
biginfo->offset, biginfo->rep_count, biginfo->max_pdu, biginfo->sdu_interval,
biginfo->max_sdu, phy2str(biginfo->phy), biginfo->framing,
biginfo->encryption ? "" : "not ");
}
static struct bt_le_per_adv_sync_cb per_adv_sync_cb = {
.synced = per_adv_sync_sync_cb,
.term = per_adv_sync_terminated_cb,
.recv = per_adv_sync_recv_cb,
.biginfo = per_adv_sync_biginfo_cb,
};
#endif /* CONFIG_BT_PER_ADV_SYNC */
static void bt_ready(int err)
{
if (err) {
shell_error(ctx_shell, "Bluetooth init failed (err %d)", err);
return;
}
shell_print(ctx_shell, "Bluetooth initialized");
if (IS_ENABLED(CONFIG_SETTINGS) && !no_settings_load) {
settings_load();
shell_print(ctx_shell, "Settings Loaded");
}
if (IS_ENABLED(CONFIG_BT_SMP_OOB_LEGACY_PAIR_ONLY)) {
bt_set_oob_data_flag(true);
}
#if defined(CONFIG_BT_OBSERVER)
bt_le_scan_cb_register(&scan_callbacks);
#endif
#if defined(CONFIG_BT_CONN)
default_conn = NULL;
bt_conn_cb_register(&conn_callbacks);
#endif /* CONFIG_BT_CONN */
#if defined(CONFIG_BT_PER_ADV_SYNC)
bt_le_per_adv_sync_cb_register(&per_adv_sync_cb);
#endif /* CONFIG_BT_PER_ADV_SYNC */
#if defined(CONFIG_BT_SMP)
bt_conn_auth_info_cb_register(&auth_info_cb);
#endif /* CONFIG_BT_SMP */
}
static int cmd_init(const struct shell *sh, size_t argc, char *argv[])
{
int err;
bool no_ready_cb = false;
ctx_shell = sh;
for (size_t argn = 1; argn < argc; argn++) {
const char *arg = argv[argn];
if (!strcmp(arg, "no-settings-load")) {
no_settings_load = true;
} else if (!strcmp(arg, "sync")) {
no_ready_cb = true;
} else {
shell_help(sh);
return SHELL_CMD_HELP_PRINTED;
}
}
if (no_ready_cb) {
err = bt_enable(bt_ready);
if (err) {
shell_error(sh, "Bluetooth init failed (err %d)",
err);
}
} else {
err = bt_enable(NULL);
bt_ready(err);
}
return err;
}
#ifdef CONFIG_SETTINGS
static int cmd_settings_load(const struct shell *sh, size_t argc,
char *argv[])
{
int err;
err = settings_load();
if (err) {
shell_error(sh, "Settings load failed (err %d)", err);
return err;
}
shell_print(sh, "Settings loaded");
return 0;
}
#endif
#if defined(CONFIG_BT_HCI)
static int cmd_hci_cmd(const struct shell *sh, size_t argc, char *argv[])
{
uint8_t ogf;
uint16_t ocf;
struct net_buf *buf = NULL, *rsp;
int err;
static uint8_t hex_data[HCI_CMD_MAX_PARAM];
int hex_data_len;
hex_data_len = 0;
ogf = strtoul(argv[1], NULL, 16);
ocf = strtoul(argv[2], NULL, 16);
if (argc > 3) {
size_t len;
if (strlen(argv[3]) > 2 * HCI_CMD_MAX_PARAM) {
shell_error(sh, "Data field too large\n");
return -ENOEXEC;
}
len = hex2bin(argv[3], strlen(argv[3]), &hex_data[hex_data_len],
sizeof(hex_data) - hex_data_len);
if (!len) {
shell_error(sh, "HCI command illegal data field\n");
return -ENOEXEC;
}
buf = bt_hci_cmd_create(BT_OP(ogf, ocf), len);
net_buf_add_mem(buf, hex_data, len);
}
err = bt_hci_cmd_send_sync(BT_OP(ogf, ocf), buf, &rsp);
if (err) {
shell_error(sh, "HCI command failed (err %d)", err);
return err;
} else {
shell_hexdump(sh, rsp->data, rsp->len);
net_buf_unref(rsp);
}
return 0;
}
#endif /* CONFIG_BT_HCI */
static int cmd_name(const struct shell *sh, size_t argc, char *argv[])
{
int err;
if (argc < 2) {
shell_print(sh, "Bluetooth Local Name: %s", bt_get_name());
return 0;
}
err = bt_set_name(argv[1]);
if (err) {
shell_error(sh, "Unable to set name %s (err %d)", argv[1],
err);
return err;
}
return 0;
}
static int cmd_appearance(const struct shell *sh, size_t argc, char *argv[])
{
if (argc == 1) {
shell_print(sh, "Bluetooth Appearance: 0x%04x", bt_get_appearance());
return 0;
}
#if defined(CONFIG_BT_DEVICE_APPEARANCE_DYNAMIC)
uint16_t app;
int err;
const char *val;
val = argv[1];
if (strlen(val) != 6 || strncmp(val, "0x", 2) ||
!hex2bin(&val[2], strlen(&val[2]), ((uint8_t *)&app), sizeof(app))) {
shell_error(sh, "Argument must be 0x followed by exactly 4 hex digits.");
return -EINVAL;
}
app = sys_be16_to_cpu(app);
err = bt_set_appearance(app);
if (err) {
shell_error(sh, "bt_set_appearance(0x%04x) failed with err %d", app, err);
return err;
}
#endif /* defined(CONFIG_BT_DEVICE_APPEARANCE_DYNAMIC) */
return 0;
}
static int cmd_id_create(const struct shell *sh, size_t argc, char *argv[])
{
char addr_str[BT_ADDR_LE_STR_LEN];
bt_addr_le_t addr;
int err;
if (argc > 1) {
err = bt_addr_le_from_str(argv[1], "random", &addr);
if (err) {
shell_error(sh, "Invalid address");
}
} else {
bt_addr_le_copy(&addr, BT_ADDR_LE_ANY);
}
err = bt_id_create(&addr, NULL);
if (err < 0) {
shell_error(sh, "Creating new ID failed (err %d)", err);
return err;
}
bt_addr_le_to_str(&addr, addr_str, sizeof(addr_str));
shell_print(sh, "New identity (%d) created: %s", err, addr_str);
return 0;
}
static int cmd_id_reset(const struct shell *sh, size_t argc, char *argv[])
{
char addr_str[BT_ADDR_LE_STR_LEN];
bt_addr_le_t addr;
uint8_t id;
int err;
if (argc < 2) {
shell_error(sh, "Identity identifier not specified");
return -ENOEXEC;
}
id = strtol(argv[1], NULL, 10);
if (argc > 2) {
err = bt_addr_le_from_str(argv[2], "random", &addr);
if (err) {
shell_print(sh, "Invalid address");
return err;
}
} else {
bt_addr_le_copy(&addr, BT_ADDR_LE_ANY);
}
err = bt_id_reset(id, &addr, NULL);
if (err < 0) {
shell_print(sh, "Resetting ID %u failed (err %d)", id, err);
return err;
}
bt_addr_le_to_str(&addr, addr_str, sizeof(addr_str));
shell_print(sh, "Identity %u reset: %s", id, addr_str);
return 0;
}
static int cmd_id_delete(const struct shell *sh, size_t argc, char *argv[])
{
uint8_t id;
int err;
if (argc < 2) {
shell_error(sh, "Identity identifier not specified");
return -ENOEXEC;
}
id = strtol(argv[1], NULL, 10);
err = bt_id_delete(id);
if (err < 0) {
shell_error(sh, "Deleting ID %u failed (err %d)", id, err);
return err;
}
shell_print(sh, "Identity %u deleted", id);
return 0;
}
static int cmd_id_show(const struct shell *sh, size_t argc, char *argv[])
{
bt_addr_le_t addrs[CONFIG_BT_ID_MAX];
size_t i, count = CONFIG_BT_ID_MAX;
bt_id_get(addrs, &count);
for (i = 0; i < count; i++) {
char addr_str[BT_ADDR_LE_STR_LEN];
bt_addr_le_to_str(&addrs[i], addr_str, sizeof(addr_str));
shell_print(sh, "%s%zu: %s", i == selected_id ? "*" : " ", i,
addr_str);
}
return 0;
}
static int cmd_id_select(const struct shell *sh, size_t argc, char *argv[])
{
char addr_str[BT_ADDR_LE_STR_LEN];
bt_addr_le_t addrs[CONFIG_BT_ID_MAX];
size_t count = CONFIG_BT_ID_MAX;
uint8_t id;
id = strtol(argv[1], NULL, 10);
bt_id_get(addrs, &count);
if (count <= id) {
shell_error(sh, "Invalid identity");
return -ENOEXEC;
}
bt_addr_le_to_str(&addrs[id], addr_str, sizeof(addr_str));
shell_print(sh, "Selected identity: %s", addr_str);
selected_id = id;
return 0;
}
#if defined(CONFIG_BT_OBSERVER)
static int cmd_active_scan_on(const struct shell *sh, uint32_t options,
uint16_t timeout)
{
int err;
struct bt_le_scan_param param = {
.type = BT_LE_SCAN_TYPE_ACTIVE,
.options = BT_LE_SCAN_OPT_NONE,
.interval = BT_GAP_SCAN_FAST_INTERVAL,
.window = BT_GAP_SCAN_FAST_WINDOW,
.timeout = timeout, };
param.options |= options;
err = bt_le_scan_start(¶m, NULL);
if (err) {
shell_error(sh, "Bluetooth set active scan failed "
"(err %d)", err);
return err;
} else {
shell_print(sh, "Bluetooth active scan enabled");
}
return 0;
}
static int cmd_passive_scan_on(const struct shell *sh, uint32_t options,
uint16_t timeout)
{
struct bt_le_scan_param param = {
.type = BT_LE_SCAN_TYPE_PASSIVE,
.options = BT_LE_SCAN_OPT_NONE,
.interval = 0x10,
.window = 0x10,
.timeout = timeout, };
int err;
param.options |= options;
err = bt_le_scan_start(¶m, NULL);
if (err) {
shell_error(sh, "Bluetooth set passive scan failed "
"(err %d)", err);
return err;
} else {
shell_print(sh, "Bluetooth passive scan enabled");
}
return 0;
}
static int cmd_scan_off(const struct shell *sh)
{
int err;
err = bt_le_scan_stop();
if (err) {
shell_error(sh, "Stopping scanning failed (err %d)", err);
return err;
} else {
shell_print(sh, "Scan successfully stopped");
}
return 0;
}
static int cmd_scan(const struct shell *sh, size_t argc, char *argv[])
{
const char *action;
uint32_t options = 0;
uint16_t timeout = 0;
/* Parse duplicate filtering data */
for (size_t argn = 2; argn < argc; argn++) {
const char *arg = argv[argn];
if (!strcmp(arg, "dups")) {
options |= BT_LE_SCAN_OPT_FILTER_DUPLICATE;
} else if (!strcmp(arg, "nodups")) {
options &= ~BT_LE_SCAN_OPT_FILTER_DUPLICATE;
} else if (!strcmp(arg, "fal")) {
options |= BT_LE_SCAN_OPT_FILTER_ACCEPT_LIST;
} else if (!strcmp(arg, "coded")) {
options |= BT_LE_SCAN_OPT_CODED;
} else if (!strcmp(arg, "no-1m")) {
options |= BT_LE_SCAN_OPT_NO_1M;
} else if (!strcmp(arg, "timeout")) {
if (++argn == argc) {
shell_help(sh);
return SHELL_CMD_HELP_PRINTED;
}
timeout = strtoul(argv[argn], NULL, 16);
} else {
shell_help(sh);
return SHELL_CMD_HELP_PRINTED;
}
}
action = argv[1];
if (!strcmp(action, "on")) {
return cmd_active_scan_on(sh, options, timeout);
} else if (!strcmp(action, "off")) {
return cmd_scan_off(sh);
} else if (!strcmp(action, "passive")) {
return cmd_passive_scan_on(sh, options, timeout);
} else {
shell_help(sh);
return SHELL_CMD_HELP_PRINTED;
}
return 0;
}
static int cmd_scan_filter_set_name(const struct shell *sh, size_t argc,
char *argv[])
{
const char *name_arg = argv[1];
if (strlen(name_arg) >= sizeof(scan_filter.name)) {
shell_error(ctx_shell, "Name is too long (max %zu): %s\n",
sizeof(scan_filter.name), name_arg);
return -ENOEXEC;
}
strcpy(scan_filter.name, name_arg);
scan_filter.name_set = true;
return 0;
}
static int cmd_scan_filter_set_addr(const struct shell *sh, size_t argc,
char *argv[])
{
const char *addr_arg = argv[1];
/* Validate length including null terminator. */
if (strlen(addr_arg) >= sizeof(scan_filter.addr)) {
shell_error(ctx_shell, "Invalid address string: %s\n",
addr_arg);
return -ENOEXEC;
}
/* Validate input to check if valid (subset of) BT address */
for (size_t i = 0; i < strlen(addr_arg); i++) {
const char c = addr_arg[i];
uint8_t tmp;
if (c != ':' && char2hex(c, &tmp) < 0) {
shell_error(ctx_shell,
"Invalid address string: %s\n",
addr_arg);
return -ENOEXEC;
}
}
strcpy(scan_filter.addr, addr_arg);
scan_filter.addr_set = true;
return 0;
}
static int cmd_scan_filter_clear_all(const struct shell *sh, size_t argc,
char *argv[])
{
(void)memset(&scan_filter, 0, sizeof(scan_filter));
return 0;
}
static int cmd_scan_filter_clear_name(const struct shell *sh, size_t argc,
char *argv[])
{
(void)memset(scan_filter.name, 0, sizeof(scan_filter.name));
scan_filter.name_set = false;
return 0;
}
static int cmd_scan_filter_clear_addr(const struct shell *sh, size_t argc,
char *argv[])
{
(void)memset(scan_filter.addr, 0, sizeof(scan_filter.addr));
scan_filter.addr_set = false;
return 0;
}
#endif /* CONFIG_BT_OBSERVER */
#if defined(CONFIG_BT_BROADCASTER)
static const struct bt_data ad_discov[] = {
BT_DATA_BYTES(BT_DATA_FLAGS, (BT_LE_AD_GENERAL | BT_LE_AD_NO_BREDR)),
};
static int cmd_advertise(const struct shell *sh, size_t argc, char *argv[])
{
struct bt_le_adv_param param = {};
const struct bt_data *ad;
size_t ad_len;
int err;
if (!strcmp(argv[1], "off")) {
if (bt_le_adv_stop() < 0) {
shell_error(sh, "Failed to stop advertising");
return -ENOEXEC;
} else {
shell_print(sh, "Advertising stopped");
}
return 0;
}
param.id = selected_id;
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 |
BT_LE_ADV_OPT_USE_NAME);
} else if (!strcmp(argv[1], "scan")) {
param.options = BT_LE_ADV_OPT_USE_NAME;
} else if (!strcmp(argv[1], "nconn")) {
param.options = 0U;
} else {
goto fail;
}
ad = ad_discov;
ad_len = ARRAY_SIZE(ad_discov);
for (size_t argn = 2; argn < argc; argn++) {
const char *arg = argv[argn];
if (!strcmp(arg, "discov")) {
/* Default */
} else if (!strcmp(arg, "non_discov")) {
ad = NULL;
ad_len = 0;
} else if (!strcmp(arg, "fal")) {
param.options |= BT_LE_ADV_OPT_FILTER_SCAN_REQ;
param.options |= BT_LE_ADV_OPT_FILTER_CONN;
} else if (!strcmp(arg, "fal-scan")) {
param.options |= BT_LE_ADV_OPT_FILTER_SCAN_REQ;
} else if (!strcmp(arg, "fal-conn")) {
param.options |= BT_LE_ADV_OPT_FILTER_CONN;
} else if (!strcmp(arg, "identity")) {
param.options |= BT_LE_ADV_OPT_USE_IDENTITY;
} else if (!strcmp(arg, "no-name")) {
param.options &= ~BT_LE_ADV_OPT_USE_NAME;
} else if (!strcmp(arg, "name-ad")) {
param.options |= BT_LE_ADV_OPT_USE_NAME;
param.options |= BT_LE_ADV_OPT_FORCE_NAME_IN_AD;
} else if (!strcmp(arg, "one-time")) {
param.options |= BT_LE_ADV_OPT_ONE_TIME;
} else if (!strcmp(arg, "disable-37")) {
param.options |= BT_LE_ADV_OPT_DISABLE_CHAN_37;
} else if (!strcmp(arg, "disable-38")) {
param.options |= BT_LE_ADV_OPT_DISABLE_CHAN_38;
} else if (!strcmp(arg, "disable-39")) {
param.options |= BT_LE_ADV_OPT_DISABLE_CHAN_39;
} else {
goto fail;
}
}
err = bt_le_adv_start(¶m, ad, ad_len, NULL, 0);
if (err < 0) {
shell_error(sh, "Failed to start advertising (err %d)",
err);
return err;
} else {
shell_print(sh, "Advertising started");
}
return 0;
fail:
shell_help(sh);
return -ENOEXEC;
}
#if defined(CONFIG_BT_PERIPHERAL)
static int cmd_directed_adv(const struct shell *sh,
size_t argc, char *argv[])
{
int err;
bt_addr_le_t addr;
struct bt_le_adv_param param;
err = bt_addr_le_from_str(argv[1], argv[2], &addr);
param = *BT_LE_ADV_CONN_DIR(&addr);
if (err) {
shell_error(sh, "Invalid peer address (err %d)", err);
return err;
}
for (size_t argn = 3; argn < argc; argn++) {
const char *arg = argv[argn];
if (!strcmp(arg, "low")) {
param.options |= BT_LE_ADV_OPT_DIR_MODE_LOW_DUTY;
param.interval_max = BT_GAP_ADV_FAST_INT_MAX_2;
param.interval_min = BT_GAP_ADV_FAST_INT_MIN_2;
} else if (!strcmp(arg, "identity")) {
param.options |= BT_LE_ADV_OPT_USE_IDENTITY;
} else if (!strcmp(arg, "dir-rpa")) {
param.options |= BT_LE_ADV_OPT_DIR_ADDR_RPA;
} else if (!strcmp(arg, "disable-37")) {
param.options |= BT_LE_ADV_OPT_DISABLE_CHAN_37;
} else if (!strcmp(arg, "disable-38")) {
param.options |= BT_LE_ADV_OPT_DISABLE_CHAN_38;
} else if (!strcmp(arg, "disable-39")) {
param.options |= BT_LE_ADV_OPT_DISABLE_CHAN_39;
} else {
shell_help(sh);
return -ENOEXEC;
}
}
err = bt_le_adv_start(¶m, NULL, 0, NULL, 0);
if (err) {
shell_error(sh, "Failed to start directed advertising (%d)",
err);
return -ENOEXEC;
} else {
shell_print(sh, "Started directed advertising");
}
return 0;
}
#endif /* CONFIG_BT_PERIPHERAL */
#if defined(CONFIG_BT_EXT_ADV)
static bool adv_param_parse(size_t argc, char *argv[],
struct bt_le_adv_param *param)
{
memset(param, 0, sizeof(struct bt_le_adv_param));
if (!strcmp(argv[1], "conn-scan")) {
param->options |= BT_LE_ADV_OPT_CONNECTABLE;
param->options |= BT_LE_ADV_OPT_SCANNABLE;
} else if (!strcmp(argv[1], "conn-nscan")) {
param->options |= BT_LE_ADV_OPT_CONNECTABLE;
} else if (!strcmp(argv[1], "nconn-scan")) {
param->options |= BT_LE_ADV_OPT_SCANNABLE;
} else if (!strcmp(argv[1], "nconn-nscan")) {
/* Acceptable option, nothing to do */
} else {
return false;
}
for (size_t argn = 2; argn < argc; argn++) {
const char *arg = argv[argn];
if (!strcmp(arg, "ext-adv")) {
param->options |= BT_LE_ADV_OPT_EXT_ADV;
} else if (!strcmp(arg, "coded")) {
param->options |= BT_LE_ADV_OPT_CODED;
} else if (!strcmp(arg, "no-2m")) {
param->options |= BT_LE_ADV_OPT_NO_2M;
} else if (!strcmp(arg, "anon")) {
param->options |= BT_LE_ADV_OPT_ANONYMOUS;
} else if (!strcmp(arg, "tx-power")) {
param->options |= BT_LE_ADV_OPT_USE_TX_POWER;
} else if (!strcmp(arg, "scan-reports")) {
param->options |= BT_LE_ADV_OPT_NOTIFY_SCAN_REQ;
} else if (!strcmp(arg, "fal")) {
param->options |= BT_LE_ADV_OPT_FILTER_SCAN_REQ;
param->options |= BT_LE_ADV_OPT_FILTER_CONN;
} else if (!strcmp(arg, "fal-scan")) {
param->options |= BT_LE_ADV_OPT_FILTER_SCAN_REQ;
} else if (!strcmp(arg, "fal-conn")) {
param->options |= BT_LE_ADV_OPT_FILTER_CONN;
} else if (!strcmp(arg, "identity")) {
param->options |= BT_LE_ADV_OPT_USE_IDENTITY;
} else if (!strcmp(arg, "name")) {
param->options |= BT_LE_ADV_OPT_USE_NAME;
} else if (!strcmp(arg, "name-ad")) {
param->options |= BT_LE_ADV_OPT_USE_NAME;
param->options |= BT_LE_ADV_OPT_FORCE_NAME_IN_AD;
} else if (!strcmp(arg, "low")) {
param->options |= BT_LE_ADV_OPT_DIR_MODE_LOW_DUTY;
} else if (!strcmp(arg, "disable-37")) {
param->options |= BT_LE_ADV_OPT_DISABLE_CHAN_37;
} else if (!strcmp(arg, "disable-38")) {
param->options |= BT_LE_ADV_OPT_DISABLE_CHAN_38;
} else if (!strcmp(arg, "disable-39")) {
param->options |= BT_LE_ADV_OPT_DISABLE_CHAN_39;
} else if (!strcmp(arg, "directed")) {
static bt_addr_le_t addr;
if ((argn + 2) >= argc) {
return false;
}
if (bt_addr_le_from_str(argv[argn + 1], argv[argn + 2],
&addr)) {
return false;
}
param->peer = &addr;
argn += 2;
} else {
return false;
}
}
param->id = selected_id;
param->sid = 0;
if (param->peer &&
!(param->options & BT_LE_ADV_OPT_DIR_MODE_LOW_DUTY)) {
param->interval_min = 0;
param->interval_max = 0;
} else {
param->interval_min = BT_GAP_ADV_FAST_INT_MIN_2;
param->interval_max = BT_GAP_ADV_FAST_INT_MAX_2;
}
return true;
}
static int cmd_adv_create(const struct shell *sh, size_t argc, char *argv[])
{
struct bt_le_adv_param param;
struct bt_le_ext_adv *adv;
uint8_t adv_index;
int err;
if (!adv_param_parse(argc, argv, ¶m)) {
shell_help(sh);
return -ENOEXEC;
}
err = bt_le_ext_adv_create(¶m, &adv_callbacks, &adv);
if (err) {
shell_error(sh, "Failed to create advertiser set (%d)", err);
return -ENOEXEC;
}
adv_index = bt_le_ext_adv_get_index(adv);
adv_sets[adv_index] = adv;
shell_print(sh, "Created adv id: %d, adv: %p", adv_index, adv);
return 0;
}
static int cmd_adv_param(const struct shell *sh, size_t argc, char *argv[])
{
struct bt_le_ext_adv *adv = adv_sets[selected_adv];
struct bt_le_adv_param param;
int err;
if (!adv_param_parse(argc, argv, ¶m)) {
shell_help(sh);
return -ENOEXEC;
}
err = bt_le_ext_adv_update_param(adv, ¶m);
if (err) {
shell_error(sh, "Failed to update advertiser set (%d)", err);
return -ENOEXEC;
}
return 0;
}
static int cmd_adv_data(const struct shell *sh, size_t argc, char *argv[])
{
uint8_t discov_data = (BT_LE_AD_GENERAL | BT_LE_AD_NO_BREDR);
struct bt_le_ext_adv *adv = adv_sets[selected_adv];
static uint8_t hex_data[1650];
struct bt_data *data;
struct bt_data ad[8];
struct bt_data sd[8];
size_t hex_data_len;
size_t ad_len = 0;
size_t sd_len = 0;
size_t *data_len;
int err;
if (!adv) {
return -EINVAL;
}
hex_data_len = 0;
data = ad;
data_len = &ad_len;
for (size_t argn = 1; argn < argc; argn++) {
const char *arg = argv[argn];
if (strcmp(arg, "scan-response") &&
*data_len == ARRAY_SIZE(ad)) {
/* Maximum entries limit reached. */
shell_print(sh, "Failed to set advertising data: "
"Maximum entries limit reached");
return -ENOEXEC;
}
if (!strcmp(arg, "discov")) {
data[*data_len].type = BT_DATA_FLAGS;
data[*data_len].data_len = sizeof(discov_data);
data[*data_len].data = &discov_data;
(*data_len)++;
} else if (!strcmp(arg, "name")) {
const char *name = bt_get_name();
data[*data_len].type = BT_DATA_NAME_COMPLETE;
data[*data_len].data_len = strlen(name);
data[*data_len].data = name;
(*data_len)++;
} else if (!strcmp(arg, "scan-response")) {
if (data == sd) {
shell_print(sh, "Failed to set advertising data: "
"duplicate scan-response option");
return -ENOEXEC;
}
data = sd;
data_len = &sd_len;
} else {
size_t len;
len = hex2bin(arg, strlen(arg), &hex_data[hex_data_len],
sizeof(hex_data) - hex_data_len);
if (!len || (len - 1) != (hex_data[hex_data_len])) {
shell_print(sh, "Failed to set advertising data: "
"malformed hex data");
return -ENOEXEC;
}
data[*data_len].type = hex_data[hex_data_len + 1];
data[*data_len].data_len = len - 2;
data[*data_len].data = &hex_data[hex_data_len + 2];
(*data_len)++;
hex_data_len += len;
}
}
err = bt_le_ext_adv_set_data(adv, ad_len > 0 ? ad : NULL, ad_len,
sd_len > 0 ? sd : NULL, sd_len);
if (err) {
shell_print(sh, "Failed to set advertising set data (%d)",
err);
return -ENOEXEC;
}
return 0;
}
static int cmd_adv_start(const struct shell *sh, size_t argc, char *argv[])
{
struct bt_le_ext_adv *adv = adv_sets[selected_adv];
struct bt_le_ext_adv_start_param param;
uint8_t num_events = 0;
int32_t timeout = 0;
int err;
if (!adv) {
shell_print(sh, "Advertiser[%d] not created", selected_adv);
return -EINVAL;
}
for (size_t argn = 1; argn < argc; argn++) {
const char *arg = argv[argn];
if (!strcmp(arg, "timeout")) {
if (++argn == argc) {
goto fail_show_help;
}
timeout = strtoul(argv[argn], NULL, 16);
}
if (!strcmp(arg, "num-events")) {
if (++argn == argc) {
goto fail_show_help;
}
num_events = strtoul(argv[argn], NULL, 16);
}
}
param.timeout = timeout;
param.num_events = num_events;
err = bt_le_ext_adv_start(adv, ¶m);
if (err) {
shell_print(sh, "Failed to start advertising set (%d)", err);
return -ENOEXEC;
}
shell_print(sh, "Advertiser[%d] %p set started", selected_adv, adv);
return 0;
fail_show_help:
shell_help(sh);
return -ENOEXEC;
}
static int cmd_adv_stop(const struct shell *sh, size_t argc, char *argv[])
{
struct bt_le_ext_adv *adv = adv_sets[selected_adv];
int err;
if (!adv) {
shell_print(sh, "Advertiser[%d] not created", selected_adv);
return -EINVAL;
}
err = bt_le_ext_adv_stop(adv);
if (err) {
shell_print(sh, "Failed to stop advertising set (%d)", err);
return -ENOEXEC;
}
shell_print(sh, "Advertiser set stopped");
return 0;
}
static int cmd_adv_delete(const struct shell *sh, size_t argc, char *argv[])
{
struct bt_le_ext_adv *adv = adv_sets[selected_adv];
int err;
if (!adv) {
shell_print(sh, "Advertiser[%d] not created", selected_adv);
return -EINVAL;
}
err = bt_le_ext_adv_delete(adv);
if (err) {
shell_error(ctx_shell, "Failed to delete advertiser set");
return err;
}
adv_sets[selected_adv] = NULL;
return 0;
}
static int cmd_adv_select(const struct shell *sh, size_t argc, char *argv[])
{
if (argc == 2) {
uint8_t id = strtol(argv[1], NULL, 10);
if (!(id < ARRAY_SIZE(adv_sets))) {
return -EINVAL;
}
selected_adv = id;
return 0;
}
for (int i = 0; i < ARRAY_SIZE(adv_sets); i++) {
if (adv_sets[i]) {
shell_print(sh, "Advertiser[%d] %p", i, adv_sets[i]);
}
}
return -ENOEXEC;
}
static int cmd_adv_info(const struct shell *sh, size_t argc, char *argv[])
{
struct bt_le_ext_adv *adv = adv_sets[selected_adv];
struct bt_le_ext_adv_info info;
int err;
if (!adv) {
return -EINVAL;
}
err = bt_le_ext_adv_get_info(adv, &info);
if (err) {
shell_error(sh, "OOB data failed");
return err;
}
shell_print(sh, "Advertiser[%d] %p", selected_adv, adv);
shell_print(sh, "Id: %d, TX power: %d dBm", info.id, info.tx_power);
return 0;
}
#if defined(CONFIG_BT_PERIPHERAL)
static int cmd_adv_oob(const struct shell *sh, size_t argc, char *argv[])
{
struct bt_le_ext_adv *adv = adv_sets[selected_adv];
int err;
if (!adv) {
return -EINVAL;
}
err = bt_le_ext_adv_oob_get_local(adv, &oob_local);
if (err) {
shell_error(sh, "OOB data failed");
return err;
}
print_le_oob(sh, &oob_local);
return 0;
}
#endif /* CONFIG_BT_PERIPHERAL */
#if defined(CONFIG_BT_PER_ADV)
static int cmd_per_adv(const struct shell *sh, size_t argc, char *argv[])
{
struct bt_le_ext_adv *adv = adv_sets[selected_adv];
if (!adv) {
shell_error(sh, "No extended advertisement set selected");
return -EINVAL;
}
if (!strcmp(argv[1], "off")) {
if (bt_le_per_adv_stop(adv) < 0) {
shell_error(sh,
"Failed to stop periodic advertising");
} else {
shell_print(sh, "Periodic advertising stopped");
}
} else if (!strcmp(argv[1], "on")) {
if (bt_le_per_adv_start(adv) < 0) {
shell_error(sh,
"Failed to start periodic advertising");
} else {
shell_print(sh, "Periodic advertising started");
}
} else {
shell_error(sh, "Invalid argument: %s", argv[1]);
return -EINVAL;
}
return 0;
}
static int cmd_per_adv_param(const struct shell *sh, size_t argc,
char *argv[])
{
struct bt_le_ext_adv *adv = adv_sets[selected_adv];
struct bt_le_per_adv_param param;
int err;
if (!adv) {
shell_error(sh, "No extended advertisement set selected");
return -EINVAL;
}
if (argc > 1) {
param.interval_min = strtol(argv[1], NULL, 16);
} else {
param.interval_min = BT_GAP_ADV_SLOW_INT_MIN;
}
if (argc > 2) {
param.interval_max = strtol(argv[2], NULL, 16);
} else {
param.interval_max = param.interval_min * 1.2;
}
if (param.interval_min > param.interval_max) {
shell_error(sh,
"Min interval shall be less than max interval");
return -EINVAL;
}
if (argc > 3 && !strcmp(argv[3], "tx-power")) {
param.options = BT_LE_ADV_OPT_USE_TX_POWER;
} else {
param.options = 0;
}
err = bt_le_per_adv_set_param(adv, ¶m);
if (err) {
shell_error(sh, "Failed to set periodic advertising "
"parameters (%d)", err);
return -ENOEXEC;
}
return 0;
}
static int cmd_per_adv_data(const struct shell *sh, size_t argc,
char *argv[])
{
struct bt_le_ext_adv *adv = adv_sets[selected_adv];
static struct bt_data ad;
static uint8_t hex_data[256];
uint8_t ad_len = 0;
int err;
if (!adv) {
shell_error(sh, "No extended advertisement set selected");
return -EINVAL;
}
memset(hex_data, 0, sizeof(hex_data));
ad_len = hex2bin(argv[1], strlen(argv[1]), hex_data, sizeof(hex_data));
if (!ad_len) {
shell_error(sh, "Could not parse adv data");
return -ENOEXEC;
}
ad.data_len = hex_data[0];
ad.type = hex_data[1];
ad.data = &hex_data[2];
err = bt_le_per_adv_set_data(adv, &ad, 1);
if (err) {
shell_error(sh,
"Failed to set periodic advertising data (%d)",
err);
return -ENOEXEC;
}
return 0;
}
#endif /* CONFIG_BT_PER_ADV */
#endif /* CONFIG_BT_EXT_ADV */
#endif /* CONFIG_BT_BROADCASTER */
#if defined(CONFIG_BT_PER_ADV_SYNC)
static int cmd_per_adv_sync_create(const struct shell *sh, size_t argc,
char *argv[])
{
int err;
struct bt_le_per_adv_sync_param create_params = { 0 };
uint32_t options = 0;
struct bt_le_per_adv_sync **free_per_adv_sync = NULL;
int i = 0;
for (i = 0; i < ARRAY_SIZE(per_adv_syncs); i++) {
if (per_adv_syncs[i] == NULL) {
free_per_adv_sync = &per_adv_syncs[i];
break;
}
}
if (i == ARRAY_SIZE(per_adv_syncs)) {
shell_error(sh, "Cannot create more per adv syncs");
return -ENOEXEC;
}
err = bt_addr_le_from_str(argv[1], argv[2], &create_params.addr);
if (err) {
shell_error(sh, "Invalid peer address (err %d)", err);
return -ENOEXEC;
}
/* Default values */
create_params.timeout = 1000; /* 10 seconds */
create_params.skip = 10;
create_params.sid = strtol(argv[3], NULL, 16);
for (int i = 4; i < argc; i++) {
if (!strcmp(argv[i], "aoa")) {
options |= BT_LE_PER_ADV_SYNC_OPT_DONT_SYNC_AOA;
} else if (!strcmp(argv[i], "aod_1us")) {
options |= BT_LE_PER_ADV_SYNC_OPT_DONT_SYNC_AOD_1US;
} else if (!strcmp(argv[i], "aod_2us")) {
options |= BT_LE_PER_ADV_SYNC_OPT_DONT_SYNC_AOD_2US;
} else if (!strcmp(argv[i], "only_cte")) {
options |=
BT_LE_PER_ADV_SYNC_OPT_SYNC_ONLY_CONST_TONE_EXT;
} else if (!strcmp(argv[i], "timeout")) {
if (++i == argc) {
shell_help(sh);
return SHELL_CMD_HELP_PRINTED;
}
create_params.timeout = strtoul(argv[i], NULL, 16);
} else if (!strcmp(argv[i], "skip")) {
if (++i == argc) {
shell_help(sh);
return SHELL_CMD_HELP_PRINTED;
}
create_params.skip = strtoul(argv[i], NULL, 16);
} else {
shell_help(sh);
return SHELL_CMD_HELP_PRINTED;
}
/* TODO: add support to parse using the per adv list */
}
create_params.options = options;
err = bt_le_per_adv_sync_create(&create_params, free_per_adv_sync);
if (err) {
shell_error(sh, "Per adv sync failed (%d)", err);
} else {
shell_print(sh, "Per adv sync pending");
}
return 0;
}
static int cmd_per_adv_sync_delete(const struct shell *sh, size_t argc,
char *argv[])
{
struct bt_le_per_adv_sync *per_adv_sync = NULL;
int index;
int err;
if (argc > 1) {
index = strtol(argv[1], NULL, 10);
} else {
index = 0;
}
if (index >= ARRAY_SIZE(per_adv_syncs)) {
shell_error(sh, "Maximum index is %zu but %d was requested",
ARRAY_SIZE(per_adv_syncs) - 1, index);
}
per_adv_sync = per_adv_syncs[index];
if (!per_adv_sync) {
return -EINVAL;
}
err = bt_le_per_adv_sync_delete(per_adv_sync);
if (err) {
shell_error(sh, "Per adv sync delete failed (%d)", err);
} else {
shell_print(sh, "Per adv sync deleted");
per_adv_syncs[index] = NULL;
}
return 0;
}
static int cmd_past_subscribe(const struct shell *sh, size_t argc,
char *argv[])
{
struct bt_le_per_adv_sync_transfer_param param;
int err;
int i = 0;
bool global = true;
if (i == ARRAY_SIZE(per_adv_syncs)) {
shell_error(sh, "Cannot create more per adv syncs");
return -ENOEXEC;
}
/* Default values */
param.options = 0;
param.timeout = 1000; /* 10 seconds */
param.skip = 10;
for (int i = 1; i < argc; i++) {
if (!strcmp(argv[i], "aoa")) {
param.options |=
BT_LE_PER_ADV_SYNC_TRANSFER_OPT_SYNC_NO_AOA;
} else if (!strcmp(argv[i], "aod_1us")) {
param.options |=
BT_LE_PER_ADV_SYNC_TRANSFER_OPT_SYNC_NO_AOD_1US;
} else if (!strcmp(argv[i], "aod_2us")) {
param.options |=
BT_LE_PER_ADV_SYNC_TRANSFER_OPT_SYNC_NO_AOD_2US;
} else if (!strcmp(argv[i], "only_cte")) {
param.options |=
BT_LE_PER_ADV_SYNC_TRANSFER_OPT_SYNC_ONLY_CTE;
} else if (!strcmp(argv[i], "timeout")) {
if (++i == argc) {
shell_help(sh);
return SHELL_CMD_HELP_PRINTED;
}
param.timeout = strtoul(argv[i], NULL, 16);
} else if (!strcmp(argv[i], "skip")) {
if (++i == argc) {
shell_help(sh);
return SHELL_CMD_HELP_PRINTED;
}
param.skip = strtoul(argv[i], NULL, 16);
} else if (!strcmp(argv[i], "conn")) {
if (!default_conn) {
shell_print(sh, "Not connected");
return -EINVAL;
}
global = false;
} else {
shell_help(sh);
return SHELL_CMD_HELP_PRINTED;
}
}
bt_le_per_adv_sync_cb_register(&per_adv_sync_cb);
err = bt_le_per_adv_sync_transfer_subscribe(
global ? NULL : default_conn, ¶m);
if (err) {
shell_error(sh, "PAST subscribe failed (%d)", err);
} else {
shell_print(sh, "Subscribed to PAST");
}
return 0;
}
static int cmd_past_unsubscribe(const struct shell *sh, size_t argc,
char *argv[])
{
int err;
if (argc > 1) {
if (!strcmp(argv[1], "conn")) {
if (default_conn) {
err =
bt_le_per_adv_sync_transfer_unsubscribe(
default_conn);
} else {
shell_print(sh, "Not connected");
return -EINVAL;
}
} else {
shell_help(sh);
return SHELL_CMD_HELP_PRINTED;
}
} else {
err = bt_le_per_adv_sync_transfer_unsubscribe(NULL);
}
if (err) {
shell_error(sh, "PAST unsubscribe failed (%d)", err);
}
return err;
}
static int cmd_per_adv_sync_transfer(const struct shell *sh, size_t argc,
char *argv[])
{
int err;
int index;
struct bt_le_per_adv_sync *per_adv_sync;
if (argc > 1) {
index = strtol(argv[1], NULL, 10);
} else {
index = 0;
}
if (index >= ARRAY_SIZE(per_adv_syncs)) {
shell_error(sh, "Maximum index is %zu but %d was requested",
ARRAY_SIZE(per_adv_syncs) - 1, index);
}
per_adv_sync = per_adv_syncs[index];
if (!per_adv_sync) {
return -EINVAL;
}
err = bt_le_per_adv_sync_transfer(per_adv_sync, default_conn, 0);
if (err) {
shell_error(sh, "Periodic advertising sync transfer failed (%d)", err);
}
return err;
}
#endif /* CONFIG_BT_PER_ADV_SYNC */
#if defined(CONFIG_BT_CONN)
#if defined(CONFIG_BT_CENTRAL)
static int cmd_connect_le(const struct shell *sh, size_t argc, char *argv[])
{
int err;
bt_addr_le_t addr;
struct bt_conn *conn;
uint32_t options = 0;
err = bt_addr_le_from_str(argv[1], argv[2], &addr);
if (err) {
shell_error(sh, "Invalid peer address (err %d)", err);
return err;
}
#if defined(CONFIG_BT_EXT_ADV)
for (size_t argn = 3; argn < argc; argn++) {
const char *arg = argv[argn];
if (!strcmp(arg, "coded")) {
options |= BT_CONN_LE_OPT_CODED;
} else if (!strcmp(arg, "no-1m")) {
options |= BT_CONN_LE_OPT_NO_1M;
} else {
shell_help(sh);
return SHELL_CMD_HELP_PRINTED;
}
}
#endif /* defined(CONFIG_BT_EXT_ADV) */
struct bt_conn_le_create_param *create_params =
BT_CONN_LE_CREATE_PARAM(options,
BT_GAP_SCAN_FAST_INTERVAL,
BT_GAP_SCAN_FAST_INTERVAL);
err = bt_conn_le_create(&addr, create_params, BT_LE_CONN_PARAM_DEFAULT,
&conn);
if (err) {
shell_error(sh, "Connection failed (%d)", err);
return -ENOEXEC;
} else {
shell_print(sh, "Connection pending");
/* unref connection obj in advance as app user */
bt_conn_unref(conn);
}
return 0;
}
#if !defined(CONFIG_BT_FILTER_ACCEPT_LIST)
static int cmd_auto_conn(const struct shell *sh, size_t argc, char *argv[])
{
bt_addr_le_t addr;
int err;
err = bt_addr_le_from_str(argv[1], argv[2], &addr);
if (err) {
shell_error(sh, "Invalid peer address (err %d)", err);
return err;
}
if (argc < 4) {
return bt_le_set_auto_conn(&addr, BT_LE_CONN_PARAM_DEFAULT);
} else if (!strcmp(argv[3], "on")) {
return bt_le_set_auto_conn(&addr, BT_LE_CONN_PARAM_DEFAULT);
} else if (!strcmp(argv[3], "off")) {
return bt_le_set_auto_conn(&addr, NULL);
} else {
shell_help(sh);
return SHELL_CMD_HELP_PRINTED;
}
return 0;
}
#endif /* !defined(CONFIG_BT_FILTER_ACCEPT_LIST) */
#endif /* CONFIG_BT_CENTRAL */
static int cmd_disconnect(const struct shell *sh, size_t 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) {
shell_help(sh);
return SHELL_CMD_HELP_PRINTED;
}
err = bt_addr_le_from_str(argv[1], argv[2], &addr);
if (err) {
shell_error(sh, "Invalid peer address (err %d)",
err);
return err;
}
conn = bt_conn_lookup_addr_le(selected_id, &addr);
}
if (!conn) {
shell_error(sh, "Not connected");
return -ENOEXEC;
}
err = bt_conn_disconnect(conn, BT_HCI_ERR_REMOTE_USER_TERM_CONN);
if (err) {
shell_error(sh, "Disconnection failed (err %d)", err);
return err;
}
bt_conn_unref(conn);
return 0;
}
static int cmd_select(const struct shell *sh, size_t argc, char *argv[])
{
struct bt_conn *conn;
bt_addr_le_t addr;
int err;
err = bt_addr_le_from_str(argv[1], argv[2], &addr);
if (err) {
shell_error(sh, "Invalid peer address (err %d)", err);
return err;
}
conn = bt_conn_lookup_addr_le(selected_id, &addr);
if (!conn) {
shell_error(sh, "No matching connection found");
return -ENOEXEC;
}
if (default_conn) {
bt_conn_unref(default_conn);
}
default_conn = conn;
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 void print_le_addr(const char *desc, const bt_addr_le_t *addr)
{
char addr_str[BT_ADDR_LE_STR_LEN];
const char *addr_desc = bt_addr_le_is_identity(addr) ? "identity" :
bt_addr_le_is_rpa(addr) ? "resolvable" :
"non-resolvable";
bt_addr_le_to_str(addr, addr_str, sizeof(addr_str));
shell_print(ctx_shell, "%s address: %s (%s)", desc, addr_str,
addr_desc);
}
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_le_t addr;
int err;
switch (argc) {
case 1:
if (default_conn) {
conn = bt_conn_ref(default_conn);
}
break;
case 2:
addr.type = BT_ADDR_LE_PUBLIC;
err = bt_addr_from_str(argv[1], &addr.a);
if (err) {
shell_error(sh, "Invalid peer address (err %d)",
err);
return err;
}
conn = bt_conn_lookup_addr_le(selected_id, &addr);
break;
case 3:
err = bt_addr_le_from_str(argv[1], argv[2], &addr);
if (err) {
shell_error(sh, "Invalid peer address (err %d)",
err);
return err;
}
conn = bt_conn_lookup_addr_le(selected_id, &addr);
break;
}
if (!conn) {
shell_error(sh, "Not connected");
return -ENOEXEC;
}
err = bt_conn_get_info(conn, &info);
if (err) {
shell_print(ctx_shell, "Failed to get info");
goto done;
}
shell_print(ctx_shell, "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_LE) {
print_le_addr("Remote", info.le.dst);
print_le_addr("Local", info.le.src);
print_le_addr("Remote on-air", info.le.remote);
print_le_addr("Local on-air", info.le.local);
shell_print(ctx_shell, "Interval: 0x%04x (%u ms)",
info.le.interval,
BT_CONN_INTERVAL_TO_MS(info.le.interval));
shell_print(ctx_shell, "Latency: 0x%04x (%u ms)",
info.le.latency,
BT_CONN_INTERVAL_TO_MS(info.le.latency));
shell_print(ctx_shell, "Supervision timeout: 0x%04x (%d ms)",
info.le.timeout, info.le.timeout * 10);
#if defined(CONFIG_BT_USER_PHY_UPDATE)
shell_print(ctx_shell, "LE PHY: TX PHY %s, RX PHY %s",
phy2str(info.le.phy->tx_phy),
phy2str(info.le.phy->rx_phy));
#endif
#if defined(CONFIG_BT_USER_DATA_LEN_UPDATE)
shell_print(ctx_shell, "LE data len: TX (len: %d time: %d)"
" RX (len: %d time: %d)",
info.le.data_len->tx_max_len,
info.le.data_len->tx_max_time,
info.le.data_len->rx_max_len,
info.le.data_len->rx_max_time);
#endif
}
#if defined(CONFIG_BT_BREDR)
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(ctx_shell, "Peer address %s", addr_str);
}
#endif /* defined(CONFIG_BT_BREDR) */
done:
bt_conn_unref(conn);
return err;
}
static int cmd_conn_update(const struct shell *sh, size_t argc, char *argv[])
{
struct bt_le_conn_param param;
int err;
if (default_conn == NULL) {
shell_error(sh,
"%s: at least, one connection is required",
sh->ctx->active_cmd.syntax);
return -ENOEXEC;
}
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) {
shell_error(sh, "conn update failed (err %d).", err);
} else {
shell_print(sh, "conn update initiated.");
}
return err;
}
#if defined(CONFIG_BT_USER_DATA_LEN_UPDATE)
static uint16_t tx_time_calc(uint8_t phy, uint16_t max_len)
{
/* Access address + header + payload + MIC + CRC */
uint16_t total_len = 4 + 2 + max_len + 4 + 3;
switch (phy) {
case BT_GAP_LE_PHY_1M:
/* 1 byte preamble, 8 us per byte */
return 8 * (1 + total_len);
case BT_GAP_LE_PHY_2M:
/* 2 byte preamble, 4 us per byte */
return 4 * (2 + total_len);
case BT_GAP_LE_PHY_CODED:
/* S8: Preamble + CI + TERM1 + 64 us per byte + TERM2 */
return 80 + 16 + 24 + 64 * (total_len) + 24;
default:
return 0;
}
}
static int cmd_conn_data_len_update(const struct shell *sh, size_t argc,
char *argv[])
{
struct bt_conn_le_data_len_param param;
int err;
if (default_conn == NULL) {
shell_error(sh,
"%s: at least, one connection is required",
sh->ctx->active_cmd.syntax);
return -ENOEXEC;
}
param.tx_max_len = strtoul(argv[1], NULL, 10);
if (argc > 2) {
param.tx_max_time = strtoul(argv[2], NULL, 10);
} else {
/* Assume 1M if not able to retrieve PHY */
uint8_t phy = BT_GAP_LE_PHY_1M;
#if defined(CONFIG_BT_USER_PHY_UPDATE)
struct bt_conn_info info;
err = bt_conn_get_info(default_conn, &info);
if (!err) {
phy = info.le.phy->tx_phy;
}
#endif
param.tx_max_time = tx_time_calc(phy, param.tx_max_len);
shell_print(sh, "Calculated tx time: %d", param.tx_max_time);
}
err = bt_conn_le_data_len_update(default_conn, ¶m);
if (err) {
shell_error(sh, "data len update failed (err %d).", err);
} else {
shell_print(sh, "data len update initiated.");
}
return err;
}
#endif
#if defined(CONFIG_BT_USER_PHY_UPDATE)
static int cmd_conn_phy_update(const struct shell *sh, size_t argc,
char *argv[])
{
struct bt_conn_le_phy_param param;
int err;
if (default_conn == NULL) {
shell_error(sh,
"%s: at least, one connection is required",
sh->ctx->active_cmd.syntax);
return -ENOEXEC;
}
param.pref_tx_phy = strtoul(argv[1], NULL, 16);
param.pref_rx_phy = param.pref_tx_phy;
param.options = BT_CONN_LE_PHY_OPT_NONE;
for (size_t argn = 2; argn < argc; argn++) {
const char *arg = argv[argn];
if (!strcmp(arg, "s2")) {
param.options |= BT_CONN_LE_PHY_OPT_CODED_S2;
} else if (!strcmp(arg, "s8")) {
param.options |= BT_CONN_LE_PHY_OPT_CODED_S8;
} else {
param.pref_rx_phy = strtoul(arg, NULL, 16);
}
}
err = bt_conn_le_phy_update(default_conn, ¶m);
if (err) {
shell_error(sh, "PHY update failed (err %d).", err);
} else {
shell_print(sh, "PHY update initiated.");
}
return err;
}
#endif
#if defined(CONFIG_BT_CENTRAL)
static int cmd_chan_map(const struct shell *sh, size_t argc, char *argv[])
{
uint8_t chan_map[5] = {};
int err;
if (hex2bin(argv[1], strlen(argv[1]), chan_map, 5) == 0) {
shell_error(sh, "Invalid channel map");
return -ENOEXEC;
}
sys_mem_swap(chan_map, 5);
err = bt_le_set_chan_map(chan_map);
if (err) {
shell_error(sh, "Failed to set channel map (err %d)", err);
} else {
shell_print(sh, "Channel map set");
}
return err;
}
#endif /* CONFIG_BT_CENTRAL */
static int cmd_oob(const struct shell *sh, size_t argc, char *argv[])
{
int err;
err = bt_le_oob_get_local(selected_id, &oob_local);
if (err) {
shell_error(sh, "OOB data failed");
return err;
}
print_le_oob(sh, &oob_local);
return 0;
}
#if defined(CONFIG_BT_SMP) || defined(CONFIG_BT_BREDR)
static int cmd_oob_remote(const struct shell *sh, size_t argc,
char *argv[])
{
int err;
bt_addr_le_t addr;
err = bt_addr_le_from_str(argv[1], argv[2], &addr);
if (err) {
shell_error(sh, "Invalid peer address (err %d)", err);
return err;
}
bt_addr_le_copy(&oob_remote.addr, &addr);
if (argc == 5) {
hex2bin(argv[3], strlen(argv[3]), oob_remote.le_sc_data.r,
sizeof(oob_remote.le_sc_data.r));
hex2bin(argv[4], strlen(argv[4]), oob_remote.le_sc_data.c,
sizeof(oob_remote.le_sc_data.c));
bt_set_oob_data_flag(true);
} else {
shell_help(sh);
return -ENOEXEC;
}
return 0;
}
static int cmd_oob_clear(const struct shell *sh, size_t argc, char *argv[])
{
memset(&oob_remote, 0, sizeof(oob_remote));
bt_set_oob_data_flag(false);
return 0;
}
#endif /* CONFIG_BT_SMP || CONFIG_BT_BREDR) */
static int cmd_clear(const struct shell *sh, size_t argc, char *argv[])
{
bt_addr_le_t addr;
int err;
if (strcmp(argv[1], "all") == 0) {
err = bt_unpair(selected_id, NULL);
if (err) {
shell_error(sh, "Failed to clear pairings (err %d)",
err);
return err;
} else {
shell_print(sh, "Pairings successfully cleared");
}
return 0;
}
if (argc < 3) {
#if defined(CONFIG_BT_BREDR)
addr.type = BT_ADDR_LE_PUBLIC;
err = bt_addr_from_str(argv[1], &addr.a);
#else
shell_print(sh, "Both address and address type needed");
return -ENOEXEC;
#endif
} else {
err = bt_addr_le_from_str(argv[1], argv[2], &addr);
}
if (err) {
shell_print(sh, "Invalid address");
return err;
}
err = bt_unpair(selected_id, &addr);
if (err) {
shell_error(sh, "Failed to clear pairing (err %d)", err);
} else {
shell_print(sh, "Pairing successfully cleared");
}
return err;
}
#endif /* CONFIG_BT_CONN */
#if defined(CONFIG_BT_SMP) || defined(CONFIG_BT_BREDR)
static int cmd_security(const struct shell *sh, size_t argc, char *argv[])
{
int err, sec;
struct bt_conn_info info;
if (!default_conn || (bt_conn_get_info(default_conn, &info) < 0)) {
shell_error(sh, "Not connected");
return -ENOEXEC;
}
sec = *argv[1] - '0';
if ((info.type == BT_CONN_TYPE_BR &&
(sec < BT_SECURITY_L0 || sec > BT_SECURITY_L3))) {
shell_error(sh, "Invalid BR/EDR security level (%d)", sec);
return -ENOEXEC;
}
if ((info.type == BT_CONN_TYPE_LE &&
(sec < BT_SECURITY_L1 || sec > BT_SECURITY_L4))) {
shell_error(sh, "Invalid LE security level (%d)", sec);
return -ENOEXEC;
}
if (argc > 2) {
if (!strcmp(argv[2], "force-pair")) {
sec |= BT_SECURITY_FORCE_PAIR;
} else {
shell_help(sh);
return -ENOEXEC;
}
}
err = bt_conn_set_security(default_conn, sec);
if (err) {
shell_error(sh, "Setting security failed (err %d)", err);
}
return err;
}
static int cmd_bondable(const struct shell *sh, size_t argc, char *argv[])
{
const char *bondable;
bondable = argv[1];
if (!strcmp(bondable, "on")) {
bt_set_bondable(true);
} else if (!strcmp(bondable, "off")) {
bt_set_bondable(false);
} else {
shell_help(sh);
return SHELL_CMD_HELP_PRINTED;
}
return 0;
}
static void bond_info(const struct bt_bond_info *info, void *user_data)
{
char addr[BT_ADDR_LE_STR_LEN];
int *bond_count = user_data;
bt_addr_le_to_str(&info->addr, addr, sizeof(addr));
shell_print(ctx_shell, "Remote Identity: %s", addr);
(*bond_count)