forked from kofal.net/zmk
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:
@@ -5,6 +5,11 @@ if ZMK_SPLIT && ZMK_SPLIT_BLE
|
||||
|
||||
menu "BLE Transport"
|
||||
|
||||
config ZMK_SPLIT_BLE_PRIORITY
|
||||
int "BLE transport priority"
|
||||
help
|
||||
Lower number priorities transports are favored over higher numbers.
|
||||
|
||||
# Added for backwards compatibility. New shields / board should set `ZMK_SPLIT_ROLE_CENTRAL` only.
|
||||
config ZMK_SPLIT_BLE_ROLE_CENTRAL
|
||||
bool
|
||||
|
||||
@@ -3,7 +3,12 @@
|
||||
|
||||
if ZMK_BLE
|
||||
|
||||
if ZMK_SPLIT_BLE && ZMK_SPLIT_ROLE_CENTRAL
|
||||
if ZMK_SPLIT_BLE
|
||||
|
||||
config ZMK_SPLIT_BLE_PRIORITY
|
||||
default 1
|
||||
|
||||
if ZMK_SPLIT_ROLE_CENTRAL
|
||||
|
||||
config ZMK_SPLIT_BLE_CENTRAL_PERIPHERALS
|
||||
default 1
|
||||
@@ -17,6 +22,8 @@ config BT_MAX_PAIRED
|
||||
#ZMK_SPLIT_BLE && ZMK_SPLIT_ROLE_CENTRAL
|
||||
endif
|
||||
|
||||
endif
|
||||
|
||||
if !ZMK_SPLIT_BLE
|
||||
|
||||
config BT_MAX_CONN
|
||||
|
||||
@@ -129,6 +129,9 @@ void release_peripheral_input_subs(struct bt_conn *conn) {
|
||||
|
||||
#endif // IS_ENABLED(CONFIG_ZMK_INPUT_SPLIT)
|
||||
|
||||
static zmk_split_transport_central_status_changed_cb_t transport_status_cb;
|
||||
static bool is_enabled;
|
||||
|
||||
static struct peripheral_slot peripherals[ZMK_SPLIT_BLE_PERIPHERAL_COUNT];
|
||||
|
||||
static bool is_scanning = false;
|
||||
@@ -253,6 +256,12 @@ int confirm_peripheral_slot_conn(struct bt_conn *conn) {
|
||||
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);
|
||||
|
||||
#if ZMK_KEYMAP_HAS_SENSORS
|
||||
|
||||
static uint8_t split_central_sensor_notify_func(struct bt_conn *conn,
|
||||
@@ -874,6 +883,11 @@ static void split_central_device_found(const bt_addr_le_t *addr, int8_t rssi, ui
|
||||
}
|
||||
|
||||
static int start_scanning(void) {
|
||||
if (!is_enabled) {
|
||||
LOG_DBG("Not scanning, we're disabled");
|
||||
return 0;
|
||||
}
|
||||
|
||||
// No action is necessary if central is already scanning.
|
||||
if (is_scanning) {
|
||||
LOG_DBG("Scanning already running");
|
||||
@@ -931,6 +945,7 @@ static void split_central_connected(struct bt_conn *conn, uint8_t conn_err) {
|
||||
|
||||
confirm_peripheral_slot_conn(conn);
|
||||
split_central_process_connection(conn);
|
||||
k_work_submit(¬ify_status_work);
|
||||
}
|
||||
|
||||
static void split_central_disconnected(struct bt_conn *conn, uint8_t reason) {
|
||||
@@ -964,9 +979,11 @@ static void split_central_disconnected(struct bt_conn *conn, uint8_t reason) {
|
||||
err = release_peripheral_slot_for_conn(conn);
|
||||
|
||||
if (err < 0) {
|
||||
return;
|
||||
LOG_WRN("Failed to release peripheral slot (%d)", err);
|
||||
}
|
||||
|
||||
k_work_submit(¬ify_status_work);
|
||||
|
||||
start_scanning();
|
||||
}
|
||||
|
||||
@@ -1112,9 +1129,9 @@ static int split_bt_invoke_behavior_payload(struct central_cmd_wrapper payload_w
|
||||
return 0;
|
||||
};
|
||||
|
||||
static int finish_init() {
|
||||
return IS_ENABLED(CONFIG_ZMK_BLE_CLEAR_BONDS_ON_START) ? 0 : start_scanning();
|
||||
}
|
||||
static int finish_init();
|
||||
|
||||
static bool settings_loaded = false;
|
||||
|
||||
#if IS_ENABLED(CONFIG_SETTINGS)
|
||||
|
||||
@@ -1189,12 +1206,84 @@ static int split_central_bt_get_available_source_ids(uint8_t *sources) {
|
||||
return count;
|
||||
}
|
||||
|
||||
static int split_central_bt_set_enabled(bool enabled) {
|
||||
is_enabled = enabled;
|
||||
if (enabled) {
|
||||
return start_scanning();
|
||||
} else {
|
||||
int err = stop_scanning();
|
||||
if (err < 0) {
|
||||
LOG_WRN("Failed to stop scanning for peripherals (%d)", err);
|
||||
}
|
||||
|
||||
for (int i = 0; i < ZMK_SPLIT_BLE_PERIPHERAL_COUNT; i++) {
|
||||
if (peripherals[i].state != PERIPHERAL_SLOT_STATE_CONNECTED) {
|
||||
continue;
|
||||
}
|
||||
|
||||
err = bt_conn_disconnect(peripherals[i].conn, BT_HCI_ERR_REMOTE_USER_TERM_CONN);
|
||||
if (err < 0) {
|
||||
LOG_WRN("Failed to disconnect a peripheral (%d)", err);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static int
|
||||
split_central_bt_set_status_callback(zmk_split_transport_central_status_changed_cb_t cb) {
|
||||
transport_status_cb = cb;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct zmk_split_transport_status split_central_bt_get_status() {
|
||||
uint8_t _source_ids[ZMK_SPLIT_BLE_PERIPHERAL_COUNT];
|
||||
|
||||
int count = split_central_bt_get_available_source_ids(_source_ids);
|
||||
|
||||
enum zmk_split_transport_connections_status conn_status;
|
||||
|
||||
if (count == 0) {
|
||||
conn_status = ZMK_SPLIT_TRANSPORT_CONNECTIONS_STATUS_DISCONNECTED;
|
||||
} else if (count == ZMK_SPLIT_BLE_PERIPHERAL_COUNT) {
|
||||
conn_status = ZMK_SPLIT_TRANSPORT_CONNECTIONS_STATUS_ALL_CONNECTED;
|
||||
} else {
|
||||
conn_status = ZMK_SPLIT_TRANSPORT_CONNECTIONS_STATUS_SOME_CONNECTED;
|
||||
}
|
||||
|
||||
return (struct zmk_split_transport_status){
|
||||
.available = !IS_ENABLED(CONFIG_ZMK_BLE_CLEAR_BONDS_ON_START) && settings_loaded,
|
||||
.enabled = is_enabled,
|
||||
.connections = conn_status,
|
||||
};
|
||||
}
|
||||
|
||||
static const struct zmk_split_transport_central_api central_api = {
|
||||
.send_command = split_central_bt_send_command,
|
||||
.get_available_source_ids = split_central_bt_get_available_source_ids,
|
||||
.set_enabled = split_central_bt_set_enabled,
|
||||
.set_status_callback = split_central_bt_set_status_callback,
|
||||
.get_status = split_central_bt_get_status,
|
||||
};
|
||||
|
||||
ZMK_SPLIT_TRANSPORT_CENTRAL_REGISTER(bt_central, ¢ral_api);
|
||||
ZMK_SPLIT_TRANSPORT_CENTRAL_REGISTER(bt_central, ¢ral_api, CONFIG_ZMK_SPLIT_BLE_PRIORITY);
|
||||
|
||||
static void notify_transport_status(void) {
|
||||
if (transport_status_cb) {
|
||||
transport_status_cb(&bt_central, split_central_bt_get_status());
|
||||
}
|
||||
}
|
||||
|
||||
static int finish_init() {
|
||||
settings_loaded = true;
|
||||
|
||||
if (!transport_status_cb) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return transport_status_cb(&bt_central, split_central_bt_get_status());
|
||||
}
|
||||
|
||||
void peripheral_event_work_callback(struct k_work *work) {
|
||||
struct peripheral_event_wrapper ev;
|
||||
|
||||
@@ -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(¬ify_status_work);
|
||||
#endif
|
||||
|
||||
return 0;
|
||||
|
||||
12
app/src/split/bluetooth/peripheral.h
Normal file
12
app/src/split/bluetooth/peripheral.h
Normal file
@@ -0,0 +1,12 @@
|
||||
/*
|
||||
* Copyright (c) 2025 The ZMK Contributors
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <zmk/split/transport/types.h>
|
||||
#include <zmk/split/transport/peripheral.h>
|
||||
|
||||
struct zmk_split_transport_peripheral *zmk_split_transport_peripheral_bt(void);
|
||||
@@ -26,6 +26,8 @@ LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);
|
||||
#include <zmk/split/bluetooth/uuid.h>
|
||||
#include <zmk/split/bluetooth/service.h>
|
||||
|
||||
#include "peripheral.h"
|
||||
|
||||
#if IS_ENABLED(CONFIG_ZMK_SPLIT_PERIPHERAL_HID_INDICATORS)
|
||||
#include <zmk/events/hid_indicators_changed.h>
|
||||
#endif // IS_ENABLED(CONFIG_ZMK_SPLIT_PERIPHERAL_HID_INDICATORS)
|
||||
@@ -343,7 +345,8 @@ static int service_init(void) {
|
||||
|
||||
SYS_INIT(service_init, APPLICATION, CONFIG_ZMK_BLE_INIT_PRIORITY);
|
||||
|
||||
static int zmk_peripheral_ble_report_event(const struct zmk_split_transport_peripheral_event *ev) {
|
||||
int zmk_split_transport_peripheral_bt_report_event(
|
||||
const struct zmk_split_transport_peripheral_event *ev) {
|
||||
switch (ev->type) {
|
||||
case ZMK_SPLIT_TRANSPORT_PERIPHERAL_EVENT_TYPE_KEY_POSITION_EVENT:
|
||||
if (ev->data.key_position_event.pressed) {
|
||||
@@ -380,12 +383,6 @@ static int zmk_peripheral_ble_report_event(const struct zmk_split_transport_peri
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct zmk_split_transport_peripheral_api peripheral_api = {
|
||||
.report_event = zmk_peripheral_ble_report_event,
|
||||
};
|
||||
|
||||
ZMK_SPLIT_TRANSPORT_PERIPHERAL_REGISTER(bt_peripheral, &peripheral_api);
|
||||
|
||||
static ssize_t split_svc_run_behavior(struct bt_conn *conn, const struct bt_gatt_attr *attrs,
|
||||
const void *buf, uint16_t len, uint16_t offset,
|
||||
uint8_t flags) {
|
||||
@@ -428,7 +425,8 @@ static ssize_t split_svc_run_behavior(struct bt_conn *conn, const struct bt_gatt
|
||||
cmd.data.invoke_behavior.param1, cmd.data.invoke_behavior.param2,
|
||||
cmd.data.invoke_behavior.state);
|
||||
|
||||
int err = zmk_split_transport_peripheral_command_handler(&bt_peripheral, cmd);
|
||||
int err = zmk_split_transport_peripheral_command_handler(
|
||||
zmk_split_transport_peripheral_bt(), cmd);
|
||||
|
||||
if (err) {
|
||||
LOG_ERR("Failed to invoke behavior %s: %d", payload->behavior_dev, err);
|
||||
|
||||
12
app/src/split/bluetooth/service.h
Normal file
12
app/src/split/bluetooth/service.h
Normal file
@@ -0,0 +1,12 @@
|
||||
/*
|
||||
* Copyright (c) 2025 The ZMK Contributors
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <zmk/split/transport/types.h>
|
||||
|
||||
int zmk_split_transport_peripheral_bt_report_event(
|
||||
const struct zmk_split_transport_peripheral_event *ev);
|
||||
Reference in New Issue
Block a user