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