diff --git a/app/Kconfig b/app/Kconfig index a5fa54f6..9f593701 100644 --- a/app/Kconfig +++ b/app/Kconfig @@ -185,6 +185,10 @@ config BT_CTLR_PHY_2M config BT_TINYCRYPT_ECC default y if BT_HCI && !BT_CTLR +config ZMK_BLE_FAST_SWITCHING + bool "Fast switching between bluetooth profiles by maintaining connection" + default y + config SYSTEM_WORKQUEUE_STACK_SIZE default 4096 if SOC_RP2040 default 2048 diff --git a/app/src/ble.c b/app/src/ble.c index a5f973a4..1e2ed6b2 100644 --- a/app/src/ble.c +++ b/app/src/ble.c @@ -168,6 +168,8 @@ int update_advertising() { enum advertising_type desired_adv = ZMK_ADV_NONE; if (zmk_ble_active_profile_is_open()) { + // We don't have an address for the active profile, so we need to open + // advertise so any interested hosts can connect. desired_adv = ZMK_ADV_CONN; } else if (!zmk_ble_active_profile_is_connected()) { desired_adv = ZMK_ADV_CONN; @@ -251,6 +253,16 @@ static int ble_save_profile() { #endif } +#if !IS_ENABLED(CONFIG_ZMK_BLE_FAST_SWITCHING) +static void zmk_ble_disconnect_inactive_profiles() { + for (int i = 0; i < ZMK_BLE_PROFILE_COUNT; i++) { + if (i != active_profile && bt_addr_le_cmp(&profiles[i].peer, BT_ADDR_LE_ANY)) { + zmk_ble_prof_disconnect(i); + } + } +} +#endif + int zmk_ble_prof_select(uint8_t index) { if (index >= ZMK_BLE_PROFILE_COUNT) { return -ERANGE; @@ -262,6 +274,14 @@ int zmk_ble_prof_select(uint8_t index) { } active_profile = index; + +#if !IS_ENABLED(CONFIG_ZMK_BLE_FAST_SWITCHING) + // If the previous profile wasn't paired, then we may be connected to + // multiple hosts during the open advertisement. And if it was, we still + // want to disconnect the previous profile. + zmk_ble_disconnect_inactive_profiles(); +#endif + ble_save_profile(); update_advertising(); @@ -420,13 +440,15 @@ static int ble_profiles_handle_set(const char *name, size_t len, settings_read_c struct settings_handler profiles_handler = {.name = "ble", .h_set = ble_profiles_handle_set}; #endif /* IS_ENABLED(CONFIG_SETTINGS) */ -static bool is_conn_active_profile(const struct bt_conn *conn) { - return bt_addr_le_cmp(bt_conn_get_dst(conn), &profiles[active_profile].peer) == 0; +static bool is_addr_active_profile(const bt_addr_le_t *addr) { + return bt_addr_le_cmp(addr, &profiles[active_profile].peer) == 0; } static void connected(struct bt_conn *conn, uint8_t err) { char addr[BT_ADDR_LE_STR_LEN]; struct bt_conn_info info; + const bt_addr_le_t *dst = bt_conn_get_dst(conn); + LOG_DBG("Connected thread: %p", k_current_get()); bt_conn_get_info(conn, &info); @@ -436,7 +458,7 @@ static void connected(struct bt_conn *conn, uint8_t err) { return; } - bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr)); + bt_addr_le_to_str(dst, addr, sizeof(addr)); advertising_status = ZMK_ADV_NONE; if (err) { @@ -453,19 +475,32 @@ static void connected(struct bt_conn *conn, uint8_t err) { } #endif // !IS_ENABLED(CONFIG_BT_GATT_AUTO_SEC_REQ) - update_advertising(); - - if (is_conn_active_profile(conn)) { + if (is_addr_active_profile(dst)) { LOG_DBG("Active profile connected"); k_work_submit(&raise_profile_changed_event_work); + } else { +#if !IS_ENABLED(CONFIG_ZMK_BLE_FAST_SWITCHING) + // If the active profile is open, then we don't know what host we want + // to connect to. We need to wait for the profile to pair before we can + // start disconnecting. + if (!zmk_ble_active_profile_is_open()) { + // A different host connected to us for some reason. Maybe we're in the + // middle of an open advertisement? + int result = bt_conn_disconnect(conn, BT_HCI_ERR_REMOTE_USER_TERM_CONN); + LOG_DBG("Disconnected from %s: %d", addr, result); + } +#endif } + + update_advertising(); } static void disconnected(struct bt_conn *conn, uint8_t reason) { char addr[BT_ADDR_LE_STR_LEN]; struct bt_conn_info info; + const bt_addr_le_t *dst = bt_conn_get_dst(conn); - bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr)); + bt_addr_le_to_str(dst, addr, sizeof(addr)); LOG_DBG("Disconnected from %s (reason 0x%02x)", addr, reason); @@ -480,7 +515,7 @@ static void disconnected(struct bt_conn *conn, uint8_t reason) { // connection for a profile as active, and not start advertising yet. k_work_submit(&update_advertising_work); - if (is_conn_active_profile(conn)) { + if (is_addr_active_profile(dst)) { LOG_DBG("Active profile disconnected"); k_work_submit(&raise_profile_changed_event_work); } @@ -595,6 +630,12 @@ static void auth_pairing_complete(struct bt_conn *conn, bool bonded) { } set_profile_address(active_profile, dst); + +#if !IS_ENABLED(CONFIG_ZMK_BLE_FAST_SWITCHING) + // We may have connected to multiple hosts while the active profile was open. + zmk_ble_disconnect_inactive_profiles(); +#endif + update_advertising(); }; diff --git a/docs/docs/config/bluetooth.md b/docs/docs/config/bluetooth.md index 9149b36b..5d18e21e 100644 --- a/docs/docs/config/bluetooth.md +++ b/docs/docs/config/bluetooth.md @@ -16,3 +16,4 @@ See [Configuration Overview](index.md) for instructions on how to change these s | `CONFIG_ZMK_BLE_EXPERIMENTAL_FEATURES` | bool | Aggregate config that enables both `CONFIG_ZMK_BLE_EXPERIMENTAL_CONN` and `CONFIG_ZMK_BLE_EXPERIMENTAL_SEC`. | n | | `CONFIG_ZMK_BLE_PASSKEY_ENTRY` | bool | Enable passkey entry during pairing for enhanced security. (Note: After enabling this, you will need to re-pair all previously paired hosts.) | n | | `CONFIG_BT_GATT_ENFORCE_SUBSCRIPTION` | bool | Low level setting for GATT subscriptions. Set to `n` to work around an annoying Windows bug with battery notifications. | y | +| `CONFIG_ZMK_BLE_FAST_SWITCHING` | bool | Enable faster switching between profiles by maintaining the bluetooth connection even after switching to a different profile. Disabling this will disconnect from (but remain paired to) the previous profile after selecting a different one. | y |