feat(split): Runtime selection of split transport (#2886)

feat(split): Runtime selection of split transport

Allow building multiple split transports, and select an active
one based on the transport availability. Wired split availability
depends on additional `detect-gpios` which must be a GPIO pin
that goes active when a wired connection is present.

feat(split): Suspend/resume wired UART devices.

To better support runtime split support, suspend/resume the UART
as necessary to save power when not using the UART.

docs(split): Document adjusting nRF52 UART interrupt priorities

For wired split on nRF52, you may need to adjust the priority for UART
interrupts lower, to ensure the interrupts used for timing sensitive
BT operations can run when needed, so document this in our pinctrl docs.

refactor(split): Restore use of aync UART on nRF52.

With fixes for Zephyr UART driver, re-enable using async API on nRF52.

fix(split): Minor wired split fixes.

Various minor fixes for wired split to avoid spurious TX in half duplex,
etc.

fix: Unconditionally define HID payloads to avoid error.

Don't conditionally define HID indicator payload, to avoid compilation
errors.

docs(split): Expand on details of split transports.

Expand the split keyboard documentation with a more fleshed out section
on the available split trasnports, and what is and isn't supported by
each, including the runtime selection functionality.

---------

Co-authored-by: Nicolas Munnich <98408764+nmunnich@users.noreply.github.com>
This commit is contained in:
Pete Johanson
2025-06-16 01:45:25 -06:00
committed by GitHub
parent 462d48b78e
commit 6b44d33db2
22 changed files with 830 additions and 93 deletions

View File

@@ -20,6 +20,9 @@
#include <zephyr/bluetooth/gatt.h>
#include <zephyr/bluetooth/hci_types.h>
#include "peripheral.h"
#include "service.h"
#if IS_ENABLED(CONFIG_SETTINGS)
#include <zephyr/settings/settings.h>
@@ -70,6 +73,7 @@ static int start_advertising(bool low_duty) {
};
static bool low_duty_advertising = false;
static bool enabled = false;
static void advertising_cb(struct k_work *work) {
const int err = start_advertising(low_duty_advertising);
@@ -86,7 +90,7 @@ static void connected(struct bt_conn *conn, uint8_t err) {
raise_zmk_split_peripheral_status_changed(
(struct zmk_split_peripheral_status_changed){.connected = is_connected});
if (err == BT_HCI_ERR_ADV_TIMEOUT) {
if (err == BT_HCI_ERR_ADV_TIMEOUT && enabled) {
low_duty_advertising = true;
k_work_submit(&advertising_work);
}
@@ -104,8 +108,10 @@ static void disconnected(struct bt_conn *conn, uint8_t reason) {
raise_zmk_split_peripheral_status_changed(
(struct zmk_split_peripheral_status_changed){.connected = is_connected});
low_duty_advertising = false;
k_work_submit(&advertising_work);
if (enabled) {
low_duty_advertising = false;
k_work_submit(&advertising_work);
}
}
static void security_changed(struct bt_conn *conn, bt_security_t level, enum bt_security_err err) {
@@ -146,6 +152,85 @@ bool zmk_split_bt_peripheral_is_connected(void) { return is_connected; }
bool zmk_split_bt_peripheral_is_bonded(void) { return is_bonded; }
static zmk_split_transport_peripheral_status_changed_cb_t transport_status_cb;
static int
split_peripheral_bt_set_status_callback(zmk_split_transport_peripheral_status_changed_cb_t cb) {
transport_status_cb = cb;
return 0;
}
static void find_first_conn(struct bt_conn *conn, void *data) {
struct bt_conn **cp = (struct bt_conn **)data;
*cp = conn;
}
static int split_peripheral_bt_set_enabled(bool en) {
int err;
enabled = en;
if (en) {
k_work_submit(&advertising_work);
return 0;
} else {
struct bt_conn *conn = NULL;
bt_conn_foreach(BT_CONN_TYPE_LE, find_first_conn, &conn);
if (conn) {
err = bt_conn_disconnect(conn, BT_HCI_ERR_REMOTE_USER_TERM_CONN);
if (err < 0) {
LOG_WRN("Failed to disconnect connection to central (%d)", err);
}
}
err = bt_le_adv_stop();
if (err < 0) {
LOG_WRN("Failed to stop advertising (%d)", err);
}
return 0;
}
}
static void notify_transport_status(void);
static void notify_status_work_cb(struct k_work *_work) { notify_transport_status(); }
static K_WORK_DEFINE(notify_status_work, notify_status_work_cb);
static bool settings_loaded = false;
static struct zmk_split_transport_status split_peripheral_bt_get_status(void) {
return (struct zmk_split_transport_status){
.available = !IS_ENABLED(CONFIG_ZMK_BLE_CLEAR_BONDS_ON_START) && settings_loaded,
.enabled = enabled,
.connections = zmk_split_bt_peripheral_is_connected()
? ZMK_SPLIT_TRANSPORT_CONNECTIONS_STATUS_ALL_CONNECTED
: ZMK_SPLIT_TRANSPORT_CONNECTIONS_STATUS_DISCONNECTED,
};
}
static const struct zmk_split_transport_peripheral_api peripheral_api = {
.report_event = zmk_split_transport_peripheral_bt_report_event,
.set_enabled = split_peripheral_bt_set_enabled,
.set_status_callback = split_peripheral_bt_set_status_callback,
.get_status = split_peripheral_bt_get_status,
};
ZMK_SPLIT_TRANSPORT_PERIPHERAL_REGISTER(bt_peripheral, &peripheral_api,
CONFIG_ZMK_SPLIT_BLE_PRIORITY);
struct zmk_split_transport_peripheral *zmk_split_transport_peripheral_bt(void) {
return &bt_peripheral;
}
static void notify_transport_status(void) {
if (transport_status_cb) {
transport_status_cb(&bt_peripheral, split_peripheral_bt_get_status());
}
}
static int zmk_peripheral_ble_complete_startup(void) {
#if IS_ENABLED(CONFIG_ZMK_BLE_CLEAR_BONDS_ON_START)
LOG_WRN("Clearing all existing BLE bond information from the keyboard");
@@ -156,7 +241,9 @@ static int zmk_peripheral_ble_complete_startup(void) {
bt_conn_auth_info_cb_register(&zmk_peripheral_ble_auth_info_cb);
low_duty_advertising = false;
k_work_submit(&advertising_work);
settings_loaded = true;
k_work_submit(&notify_status_work);
#endif
return 0;