/** @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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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 static u8_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; #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_SDP_PROTO_L2CAP) }, ) }, { 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_SDP_PROTO_RFCOMM) }, { 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 */ #define NAME_LEN 30 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 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[NAME_LEN]; (void)memset(name, 0, sizeof(name)); bt_data_parse(buf, data_cb, name); bt_addr_le_to_str(addr, le_addr, sizeof(le_addr)); print(NULL, "[DEVICE]: %s, AD evt type %u, RSSI %i %s\n", le_addr, evtype, rssi, name); } #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 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(bt_conn_get_dst(conn), 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) { error(NULL, "Failed to connect to %s (%u)\n", addr, err); goto done; } print(NULL, "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)); print(NULL, "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) { print(NULL, "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) { print(NULL, "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)); print(NULL, "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)); print(NULL, "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 hexstr2array(const char *str, u8_t *array, u8_t size) { int i, j; u8_t tmp; if (strlen(str) != ((size * 2) + (size - 1))) { return -EINVAL; } for (i = size - 1, j = 1; *str != '\0'; str++, j++) { if (!(j % 3) && (*str != ':')) { return -EINVAL; } else if (*str == ':') { i--; continue; } array[i] = array[i] << 4; if (char2hex(str, &tmp) < 0) { return -EINVAL; } array[i] |= tmp; } return 0; } static int str2bt_addr(const char *str, bt_addr_t *addr) { return hexstr2array(str, addr->val, 6); } 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 void bt_ready(int err) { if (err) { error(NULL, "Bluetooth init failed (err %d)\n", err); return; } print(NULL, "Bluetooth initialized\n"); if (IS_ENABLED(CONFIG_SETTINGS)) { settings_load(); } #if defined(CONFIG_BT_CONN) default_conn = NULL; bt_conn_cb_register(&conn_callbacks); #endif /* CONFIG_BT_CONN */ } static void cmd_init(const struct shell *shell, size_t argc, char *argv[]) { int err; err = bt_enable(bt_ready); if (err) { error(shell, "Bluetooth init failed (err %d)\n", err); } ctx_shell = shell; } #if defined(CONFIG_BT_HCI) || defined(CONFIG_BT_L2CAP_DYNAMIC_CHANNEL) static void hexdump(const struct shell *shell, const u8_t *data, size_t len) { int n = 0; while (len--) { if (n % 16 == 0) { print(shell, "%08X ", n); } print(shell, "%02X ", *data++); n++; if (n % 8 == 0) { if (n % 16 == 0) { print(shell, "\n"); } else { print(shell, " "); } } } if (n % 16) { print(shell, "\n"); } } #endif /* CONFIG_BT_HCI || CONFIG_BT_L2CAP_DYNAMIC_CHANNEL */ #if defined(CONFIG_BT_HCI) static void cmd_hci_cmd(const struct shell *shell, size_t argc, char *argv[]) { u8_t ogf; u16_t ocf; struct net_buf *buf = NULL, *rsp; int err; if (!shell_cmd_precheck(shell, (argc == 3), NULL, 0)) { return; } ogf = strtoul(argv[1], NULL, 16); ocf = strtoul(argv[2], NULL, 16); if (argc > 3) { int i; buf = bt_hci_cmd_create(BT_OP(ogf, ocf), argc - 3); for (i = 3; i < argc; i++) { net_buf_add_u8(buf, strtoul(argv[i], NULL, 16)); } } err = bt_hci_cmd_send_sync(BT_OP(ogf, ocf), buf, &rsp); if (err) { error(shell, "HCI command failed (err %d)\n", err); } else { hexdump(shell, rsp->data, rsp->len); net_buf_unref(rsp); } } #endif /* CONFIG_BT_HCI */ static void cmd_name(const struct shell *shell, size_t argc, char *argv[]) { int err; if (argc < 2) { print(shell, "Bluetooth Local Name: %s\n", bt_get_name()); } err = bt_set_name(argv[1]); if (err) { error(shell, "Unable to set name %s (err %d)", argv[1], err); } } static void cmd_id_create(const struct shell *shell, size_t argc, char *argv[]) { bt_addr_le_t addr; int err; if (argc > 1) { err = str2bt_addr_le(argv[1], "random", &addr); if (err) { error(shell, "Invalid address\n"); } } else { bt_addr_le_copy(&addr, BT_ADDR_LE_ANY); } err = bt_id_create(&addr, NULL); if (err < 0) { error(shell, "Creating new ID failed (err %d)\n", err); } print(shell, "New identity (%d) created: %s\n", err, bt_addr_le_str(&addr)); } static void cmd_id_reset(const struct shell *shell, size_t argc, char *argv[]) { bt_addr_le_t addr; u8_t id; int err; if (argc < 2) { error(shell, "Identity identifier not specified\n"); return; } id = strtol(argv[1], NULL, 10); if (argc > 2) { err = str2bt_addr_le(argv[2], "random", &addr); if (err) { print(shell, "Invalid address\n"); return; } } else { bt_addr_le_copy(&addr, BT_ADDR_LE_ANY); } err = bt_id_reset(id, &addr, NULL); if (err < 0) { print(shell, "Resetting ID %u failed (err %d)\n", id, err); return; } print(shell, "Identity %u reset: %s\n", id, bt_addr_le_str(&addr)); } static void cmd_id_delete(const struct shell *shell, size_t argc, char *argv[]) { u8_t id; int err; if (argc < 2) { error(shell, "Identity identifier not specified\n"); return; } id = strtol(argv[1], NULL, 10); err = bt_id_delete(id); if (err < 0) { error(shell, "Deleting ID %u failed (err %d)\n", id, err); return; } print(shell, "Identity %u deleted\n", id); } static void cmd_id_show(const struct shell *shell, 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++) { print(shell, "%s%zu: %s\n", i == selected_id ? "*" : " ", i, bt_addr_le_str(&addrs[i])); } } static void cmd_id_select(const struct shell *shell, size_t argc, char *argv[]) { bt_addr_le_t addrs[CONFIG_BT_ID_MAX]; size_t count = CONFIG_BT_ID_MAX; u8_t id; if (argc < 2) { shell_help_print(shell, NULL, 0); return; } id = strtol(argv[1], NULL, 10); bt_id_get(addrs, &count); if (count <= id) { error(shell, "Invalid identity\n"); return; } print(shell, "Selected identity: %s\n", bt_addr_le_str(&addrs[id])); selected_id = id; } static void cmd_active_scan_on(const struct shell *shell, 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) { error(shell, "Bluetooth set active scan failed " "(err %d)\n", err); return; } else { print(shell, "Bluetooth active scan enabled\n"); } } static void cmd_passive_scan_on(const struct shell *shell, 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) { error(shell, "Bluetooth set passive scan failed " "(err %d)\n", err); return; } else { print(shell, "Bluetooth passive scan enabled\n"); } } static void cmd_scan_off(const struct shell *shell) { int err; err = bt_le_scan_stop(); if (err) { error(shell, "Stopping scanning failed (err %d)\n", err); } else { print(shell, "Scan successfully stopped\n"); } } static void cmd_scan(const struct shell *shell, size_t argc, char *argv[]) { const char *action; int dups = -1; if (!shell_cmd_precheck(shell, (argc >= 2), NULL, 0)) { return; } /* 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 { shell_help_print(shell, NULL, 0); return; } } action = argv[1]; if (!strcmp(action, "on")) { cmd_active_scan_on(shell, dups); } else if (!strcmp(action, "off")) { cmd_scan_off(shell); } else if (!strcmp(action, "passive")) { cmd_passive_scan_on(shell, dups); } else { shell_help_print(shell, NULL, 0); return; } } static const struct bt_data ad_discov[] = { BT_DATA_BYTES(BT_DATA_FLAGS, (BT_LE_AD_GENERAL | BT_LE_AD_NO_BREDR)), }; static void cmd_advertise(const struct shell *shell, size_t 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 (!shell_cmd_precheck(shell, (argc >= 2), NULL, 0)) { return; } if (!strcmp(argv[1], "off")) { if (bt_le_adv_stop() < 0) { error(shell, "Failed to stop advertising\n"); } else { print(shell, "Advertising stopped\n"); } return; } 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 = 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) { error(shell, "Failed to start advertising (err %d)\n", err); } else { print(shell, "Advertising started\n"); } return; fail: shell_help_print(shell, NULL, 0); } #if defined(CONFIG_BT_CONN) static void cmd_connect_le(const struct shell *shell, size_t argc, char *argv[]) { int err; bt_addr_le_t addr; struct bt_conn *conn; if (argc < 3) { shell_help_print(shell, NULL, 0); return; } err = str2bt_addr_le(argv[1], argv[2], &addr); if (err) { error(shell, "Invalid peer address (err %d)\n", err); return; } conn = bt_conn_create_le(&addr, BT_LE_CONN_PARAM_DEFAULT); if (!conn) { error(shell, "Connection failed\n"); } else { print(shell, "Connection pending\n"); /* unref connection obj in advance as app user */ bt_conn_unref(conn); } } static void cmd_disconnect(const struct shell *shell, 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_print(shell, NULL, 0); return; } err = str2bt_addr_le(argv[1], argv[2], &addr); if (err) { error(shell, "Invalid peer address (err %d)\n", err); return; } conn = bt_conn_lookup_addr_le(selected_id, &addr); } if (!conn) { error(shell, "Not connected\n"); return; } err = bt_conn_disconnect(conn, BT_HCI_ERR_REMOTE_USER_TERM_CONN); if (err) { error(shell, "Disconnection failed (err %d)\n", err); } bt_conn_unref(conn); } static void cmd_auto_conn(const struct shell *shell, size_t argc, char *argv[]) { bt_addr_le_t addr; int err; if (argc < 3) { shell_help_print(shell, NULL, 0); return; } err = str2bt_addr_le(argv[1], argv[2], &addr); if (err) { error(shell, "Invalid peer address (err %d)\n", err); return; } 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 { shell_help_print(shell, NULL, 0); } } static void cmd_directed_adv(const struct shell *shell, size_t argc, char *argv[]) { int err; bt_addr_le_t addr; struct bt_conn *conn; struct bt_le_adv_param *param = BT_LE_ADV_CONN_DIR; if (!shell_cmd_precheck(shell, (argc >= 2), NULL, 0)) { return; } err = str2bt_addr_le(argv[1], argv[2], &addr); if (err) { error(shell, "Invalid peer address (err %d)\n", err); return; } if (argc > 3) { if (!strcmp(argv[3], "low")) { param = BT_LE_ADV_CONN_DIR_LOW_DUTY; } else { shell_help_print(shell, NULL, 0); return; } } conn = bt_conn_create_slave_le(&addr, param); if (!conn) { error(shell, "Failed to start directed advertising\n"); } else { print(shell, "Started directed advertising\n"); /* unref connection obj in advance as app user */ bt_conn_unref(conn); } } static void cmd_select(const struct shell *shell, size_t argc, char *argv[]) { struct bt_conn *conn; bt_addr_le_t addr; int err; if (!shell_cmd_precheck(shell, argc == 3, NULL, 0)) { return; } err = str2bt_addr_le(argv[1], argv[2], &addr); if (err) { error(shell, "Invalid peer address (err %d)\n", err); return; } conn = bt_conn_lookup_addr_le(BT_ID_DEFAULT, &addr); if (!conn) { error(shell, "No matching connection found\n"); return; } if (default_conn) { bt_conn_unref(default_conn); } default_conn = conn; } static void cmd_conn_update(const struct shell *shell, size_t argc, char *argv[]) { struct bt_le_conn_param param; int err; if (!shell_cmd_precheck(shell, argc == 5, NULL, 0)) { return; } 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) { error(shell, "conn update failed (err %d).\n", err); } else { print(shell, "conn update initiated.\n"); } } static void cmd_oob(const struct shell *shell, size_t argc, char *argv[]) { char addr[BT_ADDR_LE_STR_LEN]; struct bt_le_oob oob; int err; err = bt_le_oob_get_local(selected_id, &oob); if (err) { error(shell, "OOB data failed\n"); return; } bt_addr_le_to_str(&oob.addr, addr, sizeof(addr)); print(shell, "OOB data:\n"); print(shell, " addr %s\n", addr); } static void cmd_clear(const struct shell *shell, size_t argc, char *argv[]) { bt_addr_le_t addr; int err; if (argc < 2) { error(shell, "Specify remote address or \"all\"\n"); return; } if (strcmp(argv[1], "all") == 0) { err = bt_unpair(selected_id, NULL); if (err) { error(shell, "Failed to clear pairings (err %d)\n", err); } else { print(shell, "Pairings successfully cleared\n"); } return; } if (argc < 3) { #if defined(CONFIG_BT_BREDR) addr.type = BT_ADDR_LE_PUBLIC; err = str2bt_addr(argv[1], &addr.a); #else print(shell, "Both address and address type needed\n"); return; #endif } else { err = str2bt_addr_le(argv[1], argv[2], &addr); } if (err) { print(shell, "Invalid address\n"); return; } err = bt_unpair(selected_id, &addr); if (err) { error(shell, "Failed to clear pairing (err %d)\n", err); } else { print(shell, "Pairing successfully cleared\n"); } } static void cmd_chan_map(const struct shell *shell, size_t argc, char *argv[]) { u8_t chan_map[5]; int err; if (!shell_cmd_precheck(shell, argc == 2, NULL, 0)) { return; } err = hexstr2array(argv[1], chan_map, 5); if (err) { error(shell, "Invalid channel map\n"); return; } err = bt_le_set_chan_map(chan_map); if (err) { error(shell, "Failed to set channel map (err %d)\n", err); } else { print(shell, "Channel map set\n"); } } #endif /* CONFIG_BT_CONN */ #if defined(CONFIG_BT_SMP) || defined(CONFIG_BT_BREDR) static void cmd_security(const struct shell *shell, size_t argc, char *argv[]) { int err, sec; if (!default_conn) { error(shell, "Not connected\n"); return; } if (!shell_cmd_precheck(shell, argc == 2, NULL, 0)) { return; } sec = *argv[1] - '0'; err = bt_conn_security(default_conn, sec); if (err) { error(shell, "Setting security failed (err %d)\n", err); } } static void cmd_bondable(const struct shell *shell, size_t argc, char *argv[]) { const char *bondable; if (!shell_cmd_precheck(shell, argc == 2, NULL, 0)) { return; } bondable = argv[1]; if (!strcmp(bondable, "on")) { bt_set_bondable(true); } else if (!strcmp(bondable, "off")) { bt_set_bondable(false); } else { shell_help_print(shell, NULL, 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)); print(NULL, passkey_str, 7, "%06u", passkey); print(NULL, "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)); print(NULL, passkey_str, 7, "%06u", passkey); print(NULL, "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)); print(NULL, "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)); print(NULL, "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)); print(NULL, "Confirm pairing for %s\n", addr); } static void auth_pairing_complete(struct bt_conn *conn, bool bonded) { char addr[BT_ADDR_LE_STR_LEN]; bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr)); print(NULL, "%s with %s\n", bonded ? "Bonded" : "Paired", addr); } static void auth_pairing_failed(struct bt_conn *conn) { char addr[BT_ADDR_LE_STR_LEN]; bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr)); print(NULL, "Pairing failed with %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) { print(NULL, "Enter 16 digits wide PIN code for %s\n", addr); } else { print(NULL, "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, .pairing_failed = auth_pairing_failed, .pairing_complete = auth_pairing_complete, }; 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, .pairing_failed = auth_pairing_failed, .pairing_complete = auth_pairing_complete, }; 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, .pairing_failed = auth_pairing_failed, .pairing_complete = auth_pairing_complete, }; static struct bt_conn_auth_cb auth_cb_confirm = { #if defined(CONFIG_BT_BREDR) .pincode_entry = auth_pincode_entry, #endif .cancel = auth_cancel, .pairing_confirm = auth_pairing_confirm, .pairing_failed = auth_pairing_failed, .pairing_complete = auth_pairing_complete, }; 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, .pairing_failed = auth_pairing_failed, .pairing_complete = auth_pairing_complete, }; static void cmd_auth(const struct shell *shell, size_t argc, char *argv[]) { if (!shell_cmd_precheck(shell, argc == 2, NULL, 0)) { return; } 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], "confirm")) { bt_conn_auth_cb_register(&auth_cb_confirm); } else if (!strcmp(argv[1], "none")) { bt_conn_auth_cb_register(NULL); } else { shell_help_print(shell, NULL, 0); } } static void cmd_auth_cancel(const struct shell *shell, size_t 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) { print(shell, "Not connected\n"); return; } bt_conn_auth_cancel(conn); } static void cmd_auth_passkey_confirm(const struct shell *shell, size_t argc, char *argv[]) { if (!default_conn) { print(shell, "Not connected\n"); return; } bt_conn_auth_passkey_confirm(default_conn); } static void cmd_auth_pairing_confirm(const struct shell *shell, size_t argc, char *argv[]) { if (!default_conn) { print(shell, "Not connected\n"); return; } bt_conn_auth_pairing_confirm(default_conn); } #if defined(CONFIG_BT_FIXED_PASSKEY) static void cmd_fixed_passkey(const struct shell *shell, size_t argc, char *argv[]) { unsigned int passkey; int err; if (argc < 2) { bt_passkey_set(BT_PASSKEY_INVALID); print(shell, "Fixed passkey cleared\n"); return; } passkey = atoi(argv[1]); if (passkey > 999999) { print(shell, "Passkey should be between 0-999999\n"); return; } err = bt_passkey_set(passkey); if (err) { print(shell, "Setting fixed passkey failed (err %d)\n", err); } } #endif static void cmd_auth_passkey(const struct shell *shell, size_t argc, char *argv[]) { unsigned int passkey; if (!default_conn) { print(shell, "Not connected\n"); return; } if (!shell_cmd_precheck(shell, argc == 2, NULL, 0)) { return; } passkey = atoi(argv[1]); if (passkey > 999999) { print(shell, "Passkey should be between 0-999999\n"); return; } bt_conn_auth_passkey_entry(default_conn, passkey); } #endif /* CONFIG_BT_SMP) || CONFIG_BT_BREDR */ #if defined(CONFIG_BT_BREDR) static void cmd_auth_pincode(const struct shell *shell, size_t 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; } if (!shell_cmd_precheck(shell, argc == 2, NULL, 0)) { return; } if (strlen(argv[1]) > max) { printk("PIN code value invalid - enter max %u digits\n", max); return; } printk("PIN code \"%s\" applied\n", argv[1]); bt_conn_auth_pincode_entry(conn, argv[1]); } static void cmd_connect_bredr(const struct shell *shell, size_t argc, char *argv[]) { struct bt_conn *conn; bt_addr_t addr; int err; if (!shell_cmd_precheck(shell, argc == 2, NULL, 0)) { return; } err = str2bt_addr(argv[1], &addr); if (err) { printk("Invalid peer address (err %d)\n", err); return; } 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; } 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; (void)memset(name, 0, sizeof(name)); while (len) { if (len < 2) { break; }; /* Look for early termination */ if (!eir[0]) { break; } /* Check if field length is correct */ if (eir[0] > len - 1) { break; } switch (eir[1]) { case BT_DATA_NAME_SHORTENED: case BT_DATA_NAME_COMPLETE: if (eir[0] > sizeof(name) - 1) { memcpy(name, &eir[2], sizeof(name) - 1); } else { memcpy(name, &eir[2], eir[0] - 1); } break; default: break; } /* Parse next AD Structure */ len -= eir[0] + 1; eir += eir[0] + 1; } bt_addr_to_str(addr, br_addr, sizeof(br_addr)); 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 void cmd_bredr_discovery(const struct shell *shell, size_t argc, char *argv[]) { const char *action; if (!shell_cmd_precheck(shell, argc >= 2, NULL, 0)) { return; } 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; } printk("Discovery started\n"); } else if (!strcmp(action, "off")) { if (bt_br_discovery_stop()) { printk("Failed to stop discovery\n"); return; } printk("Discovery stopped\n"); } else { shell_help_print(shell, NULL, 0); return; } } #endif /* CONFIG_BT_BREDR */ #if defined(CONFIG_BT_L2CAP_DYNAMIC_CHANNEL) static u32_t l2cap_rate; static u32_t l2cap_recv_delay; static K_FIFO_DEFINE(l2cap_recv_fifo); struct l2ch { struct k_delayed_work recv_work; struct bt_l2cap_le_chan ch; }; #define L2CH_CHAN(_chan) CONTAINER_OF(_chan, struct l2ch, ch.chan) #define L2CH_WORK(_work) CONTAINER_OF(_work, struct l2ch, recv_work) #define L2CAP_CHAN(_chan) _chan->ch.chan static int 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; } return 0; } static void l2cap_recv_cb(struct k_work *work) { struct l2ch *c = L2CH_WORK(work); struct net_buf *buf; while ((buf = net_buf_get(&l2cap_recv_fifo, K_NO_WAIT))) { print(NULL, "Confirming reception\n"); bt_l2cap_chan_recv_complete(&c->ch.chan, buf); } } static int l2cap_recv(struct bt_l2cap_chan *chan, struct net_buf *buf) { struct l2ch *l2ch = L2CH_CHAN(chan); print(NULL, "Incoming data channel %p len %u\n", chan, buf->len); if (buf->len) { hexdump(ctx_shell, buf->data, buf->len); } if (l2cap_recv_delay) { /* Submit work only if queue is empty */ if (k_fifo_is_empty(&l2cap_recv_fifo)) { print(NULL, "Delaying response in %u ms...\n", l2cap_recv_delay); k_delayed_work_submit(&l2ch->recv_work, l2cap_recv_delay); } net_buf_put(&l2cap_recv_fifo, buf); return -EINPROGRESS; } return 0; } static void l2cap_connected(struct bt_l2cap_chan *chan) { struct l2ch *c = L2CH_CHAN(chan); k_delayed_work_init(&c->recv_work, l2cap_recv_cb); print(NULL, "Channel %p connected\n", chan); } static void l2cap_disconnected(struct bt_l2cap_chan *chan) { print(NULL, "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) { print(NULL, "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 l2ch l2ch_chan = { .ch.chan.ops = &l2cap_ops, .ch.rx.mtu = DATA_MTU, }; static int l2cap_accept(struct bt_conn *conn, struct bt_l2cap_chan **chan) { print(NULL, "Incoming conn %p\n", conn); if (l2ch_chan.ch.chan.conn) { print(NULL, "No channels available\n"); return -ENOMEM; } *chan = &l2ch_chan.ch.chan; return 0; } static struct bt_l2cap_server server = { .accept = l2cap_accept, }; static void cmd_l2cap_register(const struct shell *shell, size_t argc, char *argv[]) { if (!shell_cmd_precheck(shell, argc >= 2, NULL, 0)) { return; } if (server.psm) { error(shell, "Already registered\n"); return; } 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) { error(shell, "Unable to register psm\n"); server.psm = 0; } else { print(shell, "L2CAP psm %u sec_level %u registered\n", server.psm, server.sec_level); } } static void cmd_l2cap_connect(const struct shell *shell, size_t argc, char *argv[]) { u16_t psm; int err; if (!default_conn) { error(shell, "Not connected\n"); return; } if (!shell_cmd_precheck(shell, argc == 2, NULL, 0)) { return; } if (l2ch_chan.ch.chan.conn) { error(shell, "Channel already in use\n"); return; } psm = strtoul(argv[1], NULL, 16); err = bt_l2cap_chan_connect(default_conn, &l2ch_chan.ch.chan, psm); if (err < 0) { error(shell, "Unable to connect to psm %u (err %u)\n", psm, err); } else { print(shell, "L2CAP connection pending\n"); } } static void cmd_l2cap_disconnect(const struct shell *shell, size_t argc, char *argv[]) { int err; err = bt_l2cap_chan_disconnect(&l2ch_chan.ch.chan); if (err) { print(shell, "Unable to disconnect: %u\n", -err); } } static void cmd_l2cap_send(const struct shell *shell, size_t 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(l2ch_chan.ch.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(&l2ch_chan.ch.chan, buf); if (ret < 0) { print(shell, "Unable to send: %d\n", -ret); net_buf_unref(buf); break; } } } static void cmd_l2cap_recv(const struct shell *shell, size_t argc, char *argv[]) { if (argc > 1) { l2cap_recv_delay = strtoul(argv[1], NULL, 10); } else { print(shell, "l2cap receive delay: %u ms\n", l2cap_recv_delay); } } static void cmd_l2cap_metrics(const struct shell *shell, size_t argc, char *argv[]) { const char *action; if (argc < 2) { print(shell, "l2cap rate: %u bps.\n", l2cap_rate); return; } action = argv[1]; if (!strcmp(action, "on")) { l2cap_ops.recv = l2cap_recv_metrics; } else if (!strcmp(action, "off")) { l2cap_ops.recv = l2cap_recv; } else { shell_help_print(shell, NULL, 0); return; } print(shell, "l2cap metrics %s.\n", action); } #endif #if defined(CONFIG_BT_BREDR) static int l2cap_bredr_recv(struct bt_l2cap_chan *chan, struct net_buf *buf) { printk("Incoming data channel %p len %u\n", chan, buf->len); return 0; } 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 void cmd_bredr_l2cap_register(const struct shell *shell, size_t argc, char *argv[]) { if (!shell_cmd_precheck(shell, argc == 2, NULL, 0)) { return; } if (br_server.psm) { printk("Already registered\n"); return; } 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); } } #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 void cmd_bredr_rfcomm_register(const struct shell *shell, size_t argc, char *argv[]) { int ret; if (rfcomm_server.channel) { printk("Already registered\n"); return; } 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); } } static void cmd_rfcomm_connect(const struct shell *shell, size_t argc, char *argv[]) { u8_t channel; int err; if (!default_conn) { printk("Not connected\n"); return; } if (!shell_cmd_precheck(shell, argc == 2, NULL, 0)) { return; } 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"); } } static void cmd_rfcomm_send(const struct shell *shell, size_t 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; } } } static void cmd_rfcomm_disconnect(const struct shell *shell, size_t argc, char *argv[]) { int err; err = bt_rfcomm_dlc_disconnect(&rfcomm_dlc); if (err) { printk("Unable to disconnect: %u\n", -err); } } #endif /* CONFIG_BT_RFCOMM) */ static void cmd_bredr_discoverable(const struct shell *shell, size_t argc, char *argv[]) { int err; const char *action; if (!shell_cmd_precheck(shell, argc == 2, NULL, 0)) { return; } 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 { shell_help_print(shell, NULL, 0); return; } if (err) { printk("BR/EDR set/reset discoverable failed (err %d)\n", err); } else { printk("BR/EDR set/reset discoverable done\n"); } } static void cmd_bredr_connectable(const struct shell *shell, size_t argc, char *argv[]) { int err; const char *action; if (!shell_cmd_precheck(shell, argc == 2, NULL, 0)) { return; } action = argv[1]; if (!strcmp(action, "on")) { err = bt_br_set_connectable(true); } else if (!strcmp(action, "off")) { err = bt_br_set_connectable(false); } else { shell_help_print(shell, NULL, 0); return; } if (err) { printk("BR/EDR set/rest connectable failed (err %d)\n", err); } else { printk("BR/EDR set/reset connectable done\n"); } } static void cmd_bredr_oob(const struct shell *shell, size_t argc, char *argv[]) { char addr[BT_ADDR_STR_LEN]; struct bt_br_oob oob; int err; err = bt_br_oob_get_local(&oob); if (err) { printk("BR/EDR OOB data failed\n"); return; } bt_addr_to_str(&oob.addr, addr, sizeof(addr)); printk("BR/EDR OOB data:\n"); printk(" addr %s\n", addr); } static void cmd_bredr_sdp_find_record(const struct shell *shell, size_t argc, char *argv[]) { int err = 0, res; const char *action; if (!default_conn) { printk("Not connected\n"); return; } if (!shell_cmd_precheck(shell, argc == 2, NULL, 0)) { return; } action = argv[1]; if (!strcmp(action, "HFPAG")) { discov = discov_hfpag; } else if (!strcmp(action, "A2SRC")) { discov = discov_a2src; } else { shell_help_print(shell, NULL, 0); return; } if (err) { printk("SDP UUID to resolve not valid (err %d)\n", err); printk("Supported UUID are \'HFPAG\' \'A2SRC\' only\n"); return; } 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"); } } #endif #define HELP_NONE "[none]" #define HELP_ADDR_LE " " SHELL_CREATE_STATIC_SUBCMD_SET(bt_cmds) { SHELL_CMD(init, NULL, HELP_ADDR_LE, cmd_init), #if defined(CONFIG_BT_HCI) SHELL_CMD(hci-cmd, NULL, " [data]", cmd_hci_cmd), #endif SHELL_CMD(id-create, NULL, "[addr]", cmd_id_create), SHELL_CMD(id-reset, NULL, " [addr]", cmd_id_reset), SHELL_CMD(id-delete, NULL, "", cmd_id_delete), SHELL_CMD(id-show, NULL, HELP_NONE, cmd_id_show), SHELL_CMD(id-select, NULL, "", cmd_id_select), SHELL_CMD(name, NULL, "[name]", cmd_name), SHELL_CMD(scan, NULL, " ", cmd_scan), SHELL_CMD(advertise, NULL, " ", cmd_advertise), #if defined(CONFIG_BT_CONN) SHELL_CMD(connect, NULL, HELP_ADDR_LE, cmd_connect_le), SHELL_CMD(disconnect, NULL, HELP_NONE, cmd_disconnect), SHELL_CMD(auto-conn, NULL, HELP_ADDR_LE, cmd_auto_conn), SHELL_CMD(directed-adv, NULL, HELP_ADDR_LE " [mode: low]", cmd_directed_adv), SHELL_CMD(select, NULL, HELP_ADDR_LE, cmd_select), SHELL_CMD(conn-update, NULL, " ", cmd_conn_update), SHELL_CMD(oob, NULL, NULL, cmd_oob), SHELL_CMD(clear, NULL, NULL, cmd_clear), SHELL_CMD(channel-map, NULL, " (36-0)", cmd_chan_map), #if defined(CONFIG_BT_SMP) || defined(CONFIG_BT_BREDR) SHELL_CMD(security, NULL, "", cmd_security), SHELL_CMD(bondable, NULL, "", cmd_bondable), SHELL_CMD(auth, NULL, "", cmd_auth), SHELL_CMD(auth-cancel, NULL, HELP_NONE, cmd_auth_cancel), SHELL_CMD(auth-passkey, NULL, "", cmd_auth_passkey), SHELL_CMD(auth-passkey-confirm, NULL, HELP_NONE, cmd_auth_passkey_confirm), SHELL_CMD(auth-pairing-confirm, NULL, HELP_NONE, cmd_auth_pairing_confirm), #if defined(CONFIG_BT_FIXED_PASSKEY) SHELL_CMD(fixed-passkey, NULL, "[passkey]", cmd_fixed_passkey), #endif #if defined(CONFIG_BT_BREDR) SHELL_CMD(auth-pincode, NULL, "", cmd_auth_pincode), #endif /* CONFIG_BT_BREDR */ #endif /* CONFIG_BT_SMP || CONFIG_BT_BREDR) */ #if defined(CONFIG_BT_GATT_CLIENT) SHELL_CMD(gatt-exchange-mtu, NULL, HELP_NONE, cmd_gatt_exchange_mtu), SHELL_CMD(gatt-discover-primary, NULL, "[UUID] [start handle] [end handle]", cmd_gatt_discover), SHELL_CMD(gatt-discover-secondary, NULL, "[UUID] [start handle] [end handle]", cmd_gatt_discover), SHELL_CMD(gatt-discover-include, NULL, "[UUID] [start handle] [end handle]", cmd_gatt_discover), SHELL_CMD(gatt-discover-characteristic, NULL, "[UUID] [start handle] [end handle]", cmd_gatt_discover), SHELL_CMD(gatt-discover-descriptor, NULL, "[UUID] [start handle] [end handle]", cmd_gatt_discover), SHELL_CMD(gatt-read, NULL, " [offset]", cmd_gatt_read), SHELL_CMD(gatt-read-multiple, NULL, " ...", cmd_gatt_mread), SHELL_CMD(gatt-write, NULL, " [length]", cmd_gatt_write), SHELL_CMD(gatt-write-without-response, NULL, " [length] [repeat]", cmd_gatt_write_without_rsp), SHELL_CMD(gatt-write-signed, NULL, " [length] [repeat]", cmd_gatt_write_without_rsp), SHELL_CMD(gatt-subscribe, NULL, " [ind]", cmd_gatt_subscribe), SHELL_CMD(gatt-unsubscribe, NULL, HELP_NONE, cmd_gatt_unsubscribe), #endif /* CONFIG_BT_GATT_CLIENT */ SHELL_CMD(gatt-show-db, NULL, HELP_NONE, cmd_gatt_show_db), SHELL_CMD(gatt-register-service, NULL, "register pre-predefined test service", cmd_gatt_register_test_svc), SHELL_CMD(gatt-unregister-service, NULL, "unregister pre-predefined test service", cmd_gatt_unregister_test_svc), SHELL_CMD(gatt-metrics, NULL, "register vendr char and measure rx [value on, off]", cmd_gatt_write_cmd_metrics), #if defined(CONFIG_BT_L2CAP_DYNAMIC_CHANNEL) SHELL_CMD(l2cap-register, NULL, " [sec_level]", cmd_l2cap_register), SHELL_CMD(l2cap-connect, NULL, "", cmd_l2cap_connect), SHELL_CMD(l2cap-disconnect, NULL, HELP_NONE, cmd_l2cap_disconnect), SHELL_CMD(l2cap-send, NULL, "", cmd_l2cap_send), SHELL_CMD(l2cap-recv, NULL, "[delay (in miliseconds)", cmd_l2cap_recv), SHELL_CMD(l2cap-metrics, NULL, "", cmd_l2cap_metrics), #endif #if defined(CONFIG_BT_BREDR) SHELL_CMD(br-iscan, NULL, "", cmd_bredr_discoverable), SHELL_CMD(br-pscan, NULL, "value: on, off", cmd_bredr_connectable), SHELL_CMD(br-connect, NULL, "
", cmd_connect_bredr), SHELL_CMD(br-discovery, NULL, " [length: 1-48] [mode: limited]", cmd_bredr_discovery), SHELL_CMD(br-l2cap-register, NULL, "", cmd_bredr_l2cap_register), SHELL_CMD(br-oob, NULL, NULL, cmd_bredr_oob), SHELL_CMD(br-sdp-find, NULL, "", cmd_bredr_sdp_find_record), #if defined(CONFIG_BT_RFCOMM) SHELL_CMD(br-rfcomm-register, NULL, "", cmd_bredr_rfcomm_register), SHELL_CMD(br-rfcomm-connect, NULL, "", cmd_rfcomm_connect), SHELL_CMD(br-rfcomm-send, NULL, "", cmd_rfcomm_send), SHELL_CMD(br-rfcomm-disconnect, NULL, HELP_NONE, cmd_rfcomm_disconnect), #endif /* CONFIG_BT_RFCOMM */ #endif /* CONFIG_BT_BREDR */ #endif /* CONFIG_BT_CONN */ #if defined(CONFIG_BT_CTLR_ADV_EXT) SHELL_CMD(advx, NULL, " [coded] [anon] [txp]", cmd_advx), SHELL_CMD(scanx, NULL, " [coded]", cmd_scanx), #endif /* CONFIG_BT_CTLR_ADV_EXT */ #if defined(CONFIG_BT_CTLR_DTM) SHELL_CMD(test_tx, NULL, " ", cmd_test_tx), SHELL_CMD(test_rx, NULL, " ", cmd_test_rx), SHELL_CMD(test_end, NULL, HELP_NONE, cmd_test_end), #endif /* CONFIG_BT_CTLR_ADV_EXT */ SHELL_SUBCMD_SET_END }; static void cmd_bt(const struct shell *shell, size_t argc, char **argv) { if (argc == 1) { shell_help_print(shell, NULL, 0); return; } if (!shell_cmd_precheck(shell, (argc == 2), NULL, 0)) { return; } error(shell, "%s:%s%s\r\n", argv[0], "unknown parameter: ", argv[1]); } SHELL_CMD_REGISTER(bt, &bt_cmds, "Bluetooth shell commands", cmd_bt);