forked from kofal.net/zmk
Feature: Full-Duplex Wired Split (#2766)
refactor(split): Refactor split code for extension Extract central/peripheral code to allow for plugging in alternate transports, instead of tying all split logic to BT. feat(split): Add full-duplex wired split support * Depends on full-duplex hardware UART for communication. * Supports all existing central commands/peripheral events, including sensors/inputs from peripherals. * Only one wired split peripheral supported (for now) * Relies on chosen `zmk,split-uart` referencing the UART device. docs: Add wired split config docs. Migrate split to its own dedicated config file, and add details on wired split config. Co-authored-by: Nicolas Munnich <98408764+Nick-Munnich@users.noreply.github.com> fix: Properly override stack size on RP2040 Move the system work queue stack size override on RP2040 ouf of a `ZMK_BLE` conditional so it is properly applied generally for that SoC. --------- Co-authored-by: Nicolas Munnich <98408764+Nick-Munnich@users.noreply.github.com>
This commit is contained in:
@@ -2,7 +2,6 @@
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
if (NOT CONFIG_ZMK_SPLIT_ROLE_CENTRAL)
|
||||
target_sources(app PRIVATE split_listener.c)
|
||||
target_sources(app PRIVATE service.c)
|
||||
target_sources(app PRIVATE peripheral.c)
|
||||
endif()
|
||||
|
||||
@@ -23,6 +23,7 @@ LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);
|
||||
#include <zmk/ble.h>
|
||||
#include <zmk/behavior.h>
|
||||
#include <zmk/sensors.h>
|
||||
#include <zmk/split/transport/central.h>
|
||||
#include <zmk/split/bluetooth/uuid.h>
|
||||
#include <zmk/split/bluetooth/service.h>
|
||||
#include <zmk/event_manager.h>
|
||||
@@ -134,16 +135,15 @@ static bool is_scanning = false;
|
||||
|
||||
static const struct bt_uuid_128 split_service_uuid = BT_UUID_INIT_128(ZMK_SPLIT_BT_SERVICE_UUID);
|
||||
|
||||
K_MSGQ_DEFINE(peripheral_event_msgq, sizeof(struct zmk_position_state_changed),
|
||||
struct peripheral_event_wrapper {
|
||||
uint8_t source;
|
||||
struct zmk_split_transport_peripheral_event event;
|
||||
};
|
||||
|
||||
K_MSGQ_DEFINE(peripheral_event_msgq, sizeof(struct peripheral_event_wrapper),
|
||||
CONFIG_ZMK_SPLIT_BLE_CENTRAL_POSITION_QUEUE_SIZE, 4);
|
||||
|
||||
void peripheral_event_work_callback(struct k_work *work) {
|
||||
struct zmk_position_state_changed ev;
|
||||
while (k_msgq_get(&peripheral_event_msgq, &ev, K_NO_WAIT) == 0) {
|
||||
LOG_DBG("Trigger key position state change for %d", ev.position);
|
||||
raise_zmk_position_state_changed(ev);
|
||||
}
|
||||
}
|
||||
void peripheral_event_work_callback(struct k_work *work);
|
||||
|
||||
K_WORK_DEFINE(peripheral_event_work, peripheral_event_work_callback);
|
||||
|
||||
@@ -190,10 +190,13 @@ int release_peripheral_slot(int index) {
|
||||
for (int j = 0; j < 8; j++) {
|
||||
if (slot->position_state[i] & BIT(j)) {
|
||||
uint32_t position = (i * 8) + j;
|
||||
struct zmk_position_state_changed ev = {.source = index,
|
||||
.position = position,
|
||||
.state = false,
|
||||
.timestamp = k_uptime_get()};
|
||||
struct peripheral_event_wrapper ev = {
|
||||
.source = index,
|
||||
.event = {.type = ZMK_SPLIT_TRANSPORT_PERIPHERAL_EVENT_TYPE_KEY_POSITION_EVENT,
|
||||
.data = {.key_position_event = {
|
||||
.position = position,
|
||||
.pressed = false,
|
||||
}}}};
|
||||
|
||||
k_msgq_put(&peripheral_event_msgq, &ev, K_NO_WAIT);
|
||||
k_work_submit(&peripheral_event_work);
|
||||
@@ -251,18 +254,6 @@ int confirm_peripheral_slot_conn(struct bt_conn *conn) {
|
||||
}
|
||||
|
||||
#if ZMK_KEYMAP_HAS_SENSORS
|
||||
K_MSGQ_DEFINE(peripheral_sensor_event_msgq, sizeof(struct zmk_sensor_event),
|
||||
CONFIG_ZMK_SPLIT_BLE_CENTRAL_POSITION_QUEUE_SIZE, 4);
|
||||
|
||||
void peripheral_sensor_event_work_callback(struct k_work *work) {
|
||||
struct zmk_sensor_event ev;
|
||||
while (k_msgq_get(&peripheral_sensor_event_msgq, &ev, K_NO_WAIT) == 0) {
|
||||
LOG_DBG("Trigger sensor change for %d", ev.sensor_index);
|
||||
raise_zmk_sensor_event(ev);
|
||||
}
|
||||
}
|
||||
|
||||
K_WORK_DEFINE(peripheral_sensor_event_work, peripheral_sensor_event_work_callback);
|
||||
|
||||
static uint8_t split_central_sensor_notify_func(struct bt_conn *conn,
|
||||
struct bt_gatt_subscribe_params *params,
|
||||
@@ -282,15 +273,20 @@ static uint8_t split_central_sensor_notify_func(struct bt_conn *conn,
|
||||
|
||||
struct sensor_event sensor_event;
|
||||
memcpy(&sensor_event, data, MIN(length, sizeof(sensor_event)));
|
||||
struct zmk_sensor_event ev = {
|
||||
.sensor_index = sensor_event.sensor_index,
|
||||
.channel_data_size = MIN(sensor_event.channel_data_size, ZMK_SENSOR_EVENT_MAX_CHANNELS),
|
||||
.timestamp = k_uptime_get()};
|
||||
if (sensor_event.channel_data_size != 1) {
|
||||
return BT_GATT_ITER_STOP;
|
||||
}
|
||||
|
||||
memcpy(ev.channel_data, sensor_event.channel_data,
|
||||
sizeof(struct zmk_sensor_channel_data) * sensor_event.channel_data_size);
|
||||
k_msgq_put(&peripheral_sensor_event_msgq, &ev, K_NO_WAIT);
|
||||
k_work_submit(&peripheral_sensor_event_work);
|
||||
struct peripheral_event_wrapper event_wrapper = {
|
||||
.source = peripheral_slot_index_for_conn(conn),
|
||||
.event = {.type = ZMK_SPLIT_TRANSPORT_PERIPHERAL_EVENT_TYPE_SENSOR_EVENT,
|
||||
.data = {.sensor_event = {
|
||||
.channel_data = sensor_event.channel_data[0],
|
||||
.sensor_index = sensor_event.sensor_index,
|
||||
}}}};
|
||||
|
||||
k_msgq_put(&peripheral_event_msgq, &event_wrapper, K_NO_WAIT);
|
||||
k_work_submit(&peripheral_event_work);
|
||||
|
||||
return BT_GATT_ITER_CONTINUE;
|
||||
}
|
||||
@@ -298,27 +294,6 @@ static uint8_t split_central_sensor_notify_func(struct bt_conn *conn,
|
||||
|
||||
#if IS_ENABLED(CONFIG_ZMK_INPUT_SPLIT)
|
||||
|
||||
struct zmk_input_event_msg {
|
||||
uint8_t reg;
|
||||
struct zmk_split_input_event_payload payload;
|
||||
};
|
||||
|
||||
K_MSGQ_DEFINE(peripheral_input_event_msgq, sizeof(struct zmk_input_event_msg), 5, 4);
|
||||
// CONFIG_ZMK_SPLIT_BLE_CENTRAL_INPUT_QUEUE_SIZE, 4);
|
||||
|
||||
void peripheral_input_event_work_callback(struct k_work *work) {
|
||||
struct zmk_input_event_msg msg;
|
||||
while (k_msgq_get(&peripheral_input_event_msgq, &msg, K_NO_WAIT) == 0) {
|
||||
int ret = zmk_input_split_report_peripheral_event(
|
||||
msg.reg, msg.payload.type, msg.payload.code, msg.payload.value, msg.payload.sync);
|
||||
if (ret < 0) {
|
||||
LOG_WRN("Failed to report peripheral event %d", ret);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
K_WORK_DEFINE(input_event_work, peripheral_input_event_work_callback);
|
||||
|
||||
static uint8_t peripheral_input_event_notify_cb(struct bt_conn *conn,
|
||||
struct bt_gatt_subscribe_params *params,
|
||||
const void *data, uint16_t length) {
|
||||
@@ -335,18 +310,25 @@ static uint8_t peripheral_input_event_notify_cb(struct bt_conn *conn,
|
||||
return BT_GATT_ITER_STOP;
|
||||
}
|
||||
|
||||
struct zmk_input_event_msg msg;
|
||||
|
||||
memcpy(&msg.payload, data, MIN(length, sizeof(struct zmk_split_input_event_payload)));
|
||||
|
||||
LOG_DBG("Got an input event with type %d, code %d, value %d, sync %d", msg.payload.type,
|
||||
msg.payload.code, msg.payload.value, msg.payload.sync);
|
||||
struct zmk_split_input_event_payload payload;
|
||||
memcpy(&payload, data, MIN(length, sizeof(struct zmk_split_input_event_payload)));
|
||||
|
||||
for (size_t i = 0; i < ARRAY_SIZE(peripheral_input_slots); i++) {
|
||||
if (&peripheral_input_slots[i].sub == params) {
|
||||
msg.reg = peripheral_input_slots[i].reg;
|
||||
k_msgq_put(&peripheral_input_event_msgq, &msg, K_NO_WAIT);
|
||||
k_work_submit(&input_event_work);
|
||||
struct peripheral_event_wrapper event_wrapper = {
|
||||
.source = peripheral_slot_index_for_conn(conn),
|
||||
.event = {.type = ZMK_SPLIT_TRANSPORT_PERIPHERAL_EVENT_TYPE_INPUT_EVENT,
|
||||
.data = {.input_event = {
|
||||
.reg = peripheral_input_slots[i].reg,
|
||||
.sync = payload.sync,
|
||||
.code = payload.code,
|
||||
.type = payload.type,
|
||||
.value = payload.value,
|
||||
}}}};
|
||||
|
||||
k_msgq_put(&peripheral_event_msgq, &event_wrapper, K_NO_WAIT);
|
||||
k_work_submit(&peripheral_event_work);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -384,12 +366,13 @@ static uint8_t split_central_notify_func(struct bt_conn *conn,
|
||||
if (slot->changed_positions[i] & BIT(j)) {
|
||||
uint32_t position = (i * 8) + j;
|
||||
bool pressed = slot->position_state[i] & BIT(j);
|
||||
struct zmk_position_state_changed ev = {.source =
|
||||
peripheral_slot_index_for_conn(conn),
|
||||
.position = position,
|
||||
.state = pressed,
|
||||
.timestamp = k_uptime_get()};
|
||||
|
||||
struct peripheral_event_wrapper ev = {
|
||||
.source = peripheral_slot_index_for_conn(conn),
|
||||
.event = {.type = ZMK_SPLIT_TRANSPORT_PERIPHERAL_EVENT_TYPE_KEY_POSITION_EVENT,
|
||||
.data = {.key_position_event = {
|
||||
.position = position,
|
||||
.pressed = pressed,
|
||||
}}}};
|
||||
k_msgq_put(&peripheral_event_msgq, &ev, K_NO_WAIT);
|
||||
k_work_submit(&peripheral_event_work);
|
||||
}
|
||||
@@ -401,35 +384,6 @@ static uint8_t split_central_notify_func(struct bt_conn *conn,
|
||||
|
||||
#if IS_ENABLED(CONFIG_ZMK_SPLIT_BLE_CENTRAL_BATTERY_LEVEL_FETCHING)
|
||||
|
||||
static uint8_t peripheral_battery_levels[ZMK_SPLIT_BLE_PERIPHERAL_COUNT] = {0};
|
||||
|
||||
int zmk_split_get_peripheral_battery_level(uint8_t source, uint8_t *level) {
|
||||
if (source >= ARRAY_SIZE(peripheral_battery_levels)) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (peripherals[source].state != PERIPHERAL_SLOT_STATE_CONNECTED) {
|
||||
return -ENOTCONN;
|
||||
}
|
||||
|
||||
*level = peripheral_battery_levels[source];
|
||||
return 0;
|
||||
}
|
||||
|
||||
K_MSGQ_DEFINE(peripheral_batt_lvl_msgq, sizeof(struct zmk_peripheral_battery_state_changed),
|
||||
CONFIG_ZMK_SPLIT_BLE_CENTRAL_BATTERY_LEVEL_QUEUE_SIZE, 4);
|
||||
|
||||
void peripheral_batt_lvl_change_callback(struct k_work *work) {
|
||||
struct zmk_peripheral_battery_state_changed ev;
|
||||
while (k_msgq_get(&peripheral_batt_lvl_msgq, &ev, K_NO_WAIT) == 0) {
|
||||
LOG_DBG("Triggering peripheral battery level change %u", ev.state_of_charge);
|
||||
peripheral_battery_levels[ev.source] = ev.state_of_charge;
|
||||
raise_zmk_peripheral_battery_state_changed(ev);
|
||||
}
|
||||
}
|
||||
|
||||
K_WORK_DEFINE(peripheral_batt_lvl_work, peripheral_batt_lvl_change_callback);
|
||||
|
||||
static uint8_t split_central_battery_level_notify_func(struct bt_conn *conn,
|
||||
struct bt_gatt_subscribe_params *params,
|
||||
const void *data, uint16_t length) {
|
||||
@@ -454,10 +408,16 @@ static uint8_t split_central_battery_level_notify_func(struct bt_conn *conn,
|
||||
LOG_DBG("[BATTERY LEVEL NOTIFICATION] data %p length %u", data, length);
|
||||
uint8_t battery_level = ((uint8_t *)data)[0];
|
||||
LOG_DBG("Battery level: %u", battery_level);
|
||||
struct zmk_peripheral_battery_state_changed ev = {
|
||||
.source = peripheral_slot_index_for_conn(conn), .state_of_charge = battery_level};
|
||||
k_msgq_put(&peripheral_batt_lvl_msgq, &ev, K_NO_WAIT);
|
||||
k_work_submit(&peripheral_batt_lvl_work);
|
||||
|
||||
struct peripheral_event_wrapper ev = {
|
||||
.source = peripheral_slot_index_for_conn(conn),
|
||||
.event = {.type = ZMK_SPLIT_TRANSPORT_PERIPHERAL_EVENT_TYPE_BATTERY_EVENT,
|
||||
.data = {.battery_event = {
|
||||
.level = battery_level,
|
||||
}}}};
|
||||
|
||||
k_msgq_put(&peripheral_event_msgq, &ev, K_NO_WAIT);
|
||||
k_work_submit(&peripheral_event_work);
|
||||
|
||||
return BT_GATT_ITER_CONTINUE;
|
||||
}
|
||||
@@ -493,10 +453,15 @@ static uint8_t split_central_battery_level_read_func(struct bt_conn *conn, uint8
|
||||
|
||||
LOG_DBG("Battery level: %u", battery_level);
|
||||
|
||||
struct zmk_peripheral_battery_state_changed ev = {
|
||||
.source = peripheral_slot_index_for_conn(conn), .state_of_charge = battery_level};
|
||||
k_msgq_put(&peripheral_batt_lvl_msgq, &ev, K_NO_WAIT);
|
||||
k_work_submit(&peripheral_batt_lvl_work);
|
||||
struct peripheral_event_wrapper ev = {
|
||||
.source = peripheral_slot_index_for_conn(conn),
|
||||
.event = {.type = ZMK_SPLIT_TRANSPORT_PERIPHERAL_EVENT_TYPE_BATTERY_EVENT,
|
||||
.data = {.battery_event = {
|
||||
.level = battery_level,
|
||||
}}}};
|
||||
|
||||
k_msgq_put(&peripheral_event_msgq, &ev, K_NO_WAIT);
|
||||
k_work_submit(&peripheral_event_work);
|
||||
|
||||
return BT_GATT_ITER_CONTINUE;
|
||||
}
|
||||
@@ -977,10 +942,19 @@ static void split_central_disconnected(struct bt_conn *conn, uint8_t reason) {
|
||||
LOG_DBG("Disconnected: %s (reason %d)", addr, reason);
|
||||
|
||||
#if IS_ENABLED(CONFIG_ZMK_SPLIT_BLE_CENTRAL_BATTERY_LEVEL_FETCHING)
|
||||
struct zmk_peripheral_battery_state_changed ev = {
|
||||
.source = peripheral_slot_index_for_conn(conn), .state_of_charge = 0};
|
||||
k_msgq_put(&peripheral_batt_lvl_msgq, &ev, K_NO_WAIT);
|
||||
k_work_submit(&peripheral_batt_lvl_work);
|
||||
struct peripheral_event_wrapper ev = {
|
||||
.source = peripheral_slot_index_for_conn(conn),
|
||||
.event = {.type = ZMK_SPLIT_TRANSPORT_PERIPHERAL_EVENT_TYPE_BATTERY_EVENT,
|
||||
.data = {.battery_event = {
|
||||
.level = 0,
|
||||
}}}};
|
||||
|
||||
k_msgq_put(&peripheral_event_msgq, &ev, K_NO_WAIT);
|
||||
k_work_submit(&peripheral_event_work);
|
||||
// struct zmk_peripheral_battery_state_changed ev = {
|
||||
// .source = peripheral_slot_index_for_conn(conn), .state_of_charge = 0};
|
||||
// k_msgq_put(&peripheral_batt_lvl_msgq, &ev, K_NO_WAIT);
|
||||
// k_work_submit(&peripheral_batt_lvl_work);
|
||||
#endif // IS_ENABLED(CONFIG_ZMK_SPLIT_BLE_CENTRAL_BATTERY_LEVEL_FETCHING)
|
||||
|
||||
#if IS_ENABLED(CONFIG_ZMK_INPUT_SPLIT)
|
||||
@@ -1027,17 +1001,16 @@ K_THREAD_STACK_DEFINE(split_central_split_run_q_stack,
|
||||
|
||||
struct k_work_q split_central_split_run_q;
|
||||
|
||||
struct zmk_split_run_behavior_payload_wrapper {
|
||||
struct central_cmd_wrapper {
|
||||
uint8_t source;
|
||||
struct zmk_split_run_behavior_payload payload;
|
||||
struct zmk_split_transport_central_command cmd;
|
||||
};
|
||||
|
||||
K_MSGQ_DEFINE(zmk_split_central_split_run_msgq,
|
||||
sizeof(struct zmk_split_run_behavior_payload_wrapper),
|
||||
K_MSGQ_DEFINE(zmk_split_central_split_run_msgq, sizeof(struct central_cmd_wrapper),
|
||||
CONFIG_ZMK_SPLIT_BLE_CENTRAL_SPLIT_RUN_QUEUE_SIZE, 4);
|
||||
|
||||
void split_central_split_run_callback(struct k_work *work) {
|
||||
struct zmk_split_run_behavior_payload_wrapper payload_wrapper;
|
||||
struct central_cmd_wrapper payload_wrapper;
|
||||
|
||||
LOG_DBG("");
|
||||
|
||||
@@ -1046,34 +1019,85 @@ void split_central_split_run_callback(struct k_work *work) {
|
||||
LOG_ERR("Source not connected");
|
||||
continue;
|
||||
}
|
||||
if (!peripherals[payload_wrapper.source].run_behavior_handle) {
|
||||
LOG_ERR("Run behavior handle not found");
|
||||
continue;
|
||||
|
||||
switch (payload_wrapper.cmd.type) {
|
||||
case ZMK_SPLIT_TRANSPORT_CENTRAL_CMD_TYPE_INVOKE_BEHAVIOR: {
|
||||
if (!peripherals[payload_wrapper.source].run_behavior_handle) {
|
||||
LOG_ERR("Run behavior handle not found");
|
||||
continue;
|
||||
}
|
||||
|
||||
struct zmk_split_run_behavior_payload payload = {
|
||||
.data = {
|
||||
.param1 = payload_wrapper.cmd.data.invoke_behavior.param1,
|
||||
.param2 = payload_wrapper.cmd.data.invoke_behavior.param2,
|
||||
.position = payload_wrapper.cmd.data.invoke_behavior.position,
|
||||
.source = payload_wrapper.cmd.data.invoke_behavior.event_source,
|
||||
.state = payload_wrapper.cmd.data.invoke_behavior.state ? 1 : 0,
|
||||
}};
|
||||
const size_t payload_dev_size = sizeof(payload.behavior_dev);
|
||||
if (strlcpy(payload.behavior_dev, payload_wrapper.cmd.data.invoke_behavior.behavior_dev,
|
||||
payload_dev_size) >= payload_dev_size) {
|
||||
LOG_ERR("Truncated behavior label %s to %s before invoking peripheral behavior",
|
||||
payload_wrapper.cmd.data.invoke_behavior.behavior_dev,
|
||||
payload.behavior_dev);
|
||||
}
|
||||
|
||||
int err = bt_gatt_write_without_response(
|
||||
peripherals[payload_wrapper.source].conn,
|
||||
peripherals[payload_wrapper.source].run_behavior_handle, &payload,
|
||||
sizeof(struct zmk_split_run_behavior_payload), true);
|
||||
|
||||
if (err) {
|
||||
LOG_ERR("Failed to write the behavior characteristic (err %d)", err);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ZMK_SPLIT_TRANSPORT_CENTRAL_CMD_TYPE_SET_PHYSICAL_LAYOUT:
|
||||
update_peripheral_selected_layout(
|
||||
&peripherals[payload_wrapper.source],
|
||||
payload_wrapper.cmd.data.set_physical_layout.layout_idx);
|
||||
break;
|
||||
#if IS_ENABLED(CONFIG_ZMK_SPLIT_PERIPHERAL_HID_INDICATORS)
|
||||
case ZMK_SPLIT_TRANSPORT_CENTRAL_CMD_TYPE_SET_HID_INDICATORS:
|
||||
LOG_WRN("do the indicators dance");
|
||||
if (peripherals[payload_wrapper.source].update_hid_indicators == 0) {
|
||||
// It appears that sometimes the peripheral is considered connected
|
||||
// before the GATT characteristics have been discovered. If this is
|
||||
// the case, the update_hid_indicators handle will not yet be set.
|
||||
LOG_WRN("NO HANDLE TO SET ON PERIPHERAL");
|
||||
break;
|
||||
}
|
||||
|
||||
int err = bt_gatt_write_without_response(
|
||||
peripherals[payload_wrapper.source].conn,
|
||||
peripherals[payload_wrapper.source].run_behavior_handle, &payload_wrapper.payload,
|
||||
sizeof(struct zmk_split_run_behavior_payload), true);
|
||||
int err = bt_gatt_write_without_response(
|
||||
peripherals[payload_wrapper.source].conn,
|
||||
peripherals[payload_wrapper.source].update_hid_indicators,
|
||||
&payload_wrapper.cmd.data.set_hid_indicators.indicators,
|
||||
sizeof(payload_wrapper.cmd.data.set_hid_indicators.indicators), true);
|
||||
|
||||
if (err) {
|
||||
LOG_ERR("Failed to write the behavior characteristic (err %d)", err);
|
||||
if (err) {
|
||||
LOG_ERR("Failed to write HID indicator characteristic (err %d)", err);
|
||||
}
|
||||
break;
|
||||
#endif // IS_ENABLED(CONFIG_ZMK_SPLIT_PERIPHERAL_HID_INDICATORS)
|
||||
default:
|
||||
LOG_WRN("Unsupported wrapped central command type %d", payload_wrapper.cmd.type);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
K_WORK_DEFINE(split_central_split_run_work, split_central_split_run_callback);
|
||||
|
||||
static int
|
||||
split_bt_invoke_behavior_payload(struct zmk_split_run_behavior_payload_wrapper payload_wrapper) {
|
||||
static int split_bt_invoke_behavior_payload(struct central_cmd_wrapper payload_wrapper) {
|
||||
LOG_DBG("");
|
||||
|
||||
int err = k_msgq_put(&zmk_split_central_split_run_msgq, &payload_wrapper, K_MSEC(100));
|
||||
if (err) {
|
||||
switch (err) {
|
||||
case -EAGAIN: {
|
||||
LOG_WRN("Consumer message queue full, popping first message and queueing again");
|
||||
struct zmk_split_run_behavior_payload_wrapper discarded_report;
|
||||
LOG_WRN("Run command message queue full, popping first message and queueing again");
|
||||
struct central_cmd_wrapper discarded_report;
|
||||
k_msgq_get(&zmk_split_central_split_run_msgq, &discarded_report, K_NO_WAIT);
|
||||
return split_bt_invoke_behavior_payload(payload_wrapper);
|
||||
}
|
||||
@@ -1088,63 +1112,6 @@ split_bt_invoke_behavior_payload(struct zmk_split_run_behavior_payload_wrapper p
|
||||
return 0;
|
||||
};
|
||||
|
||||
int zmk_split_bt_invoke_behavior(uint8_t source, struct zmk_behavior_binding *binding,
|
||||
struct zmk_behavior_binding_event event, bool state) {
|
||||
struct zmk_split_run_behavior_payload payload = {.data = {
|
||||
.param1 = binding->param1,
|
||||
.param2 = binding->param2,
|
||||
.position = event.position,
|
||||
.source = event.source,
|
||||
.state = state ? 1 : 0,
|
||||
}};
|
||||
const size_t payload_dev_size = sizeof(payload.behavior_dev);
|
||||
if (strlcpy(payload.behavior_dev, binding->behavior_dev, payload_dev_size) >=
|
||||
payload_dev_size) {
|
||||
LOG_ERR("Truncated behavior label %s to %s before invoking peripheral behavior",
|
||||
binding->behavior_dev, payload.behavior_dev);
|
||||
}
|
||||
|
||||
struct zmk_split_run_behavior_payload_wrapper wrapper = {.source = source, .payload = payload};
|
||||
return split_bt_invoke_behavior_payload(wrapper);
|
||||
}
|
||||
|
||||
#if IS_ENABLED(CONFIG_ZMK_SPLIT_PERIPHERAL_HID_INDICATORS)
|
||||
|
||||
static zmk_hid_indicators_t hid_indicators = 0;
|
||||
|
||||
static void split_central_update_indicators_callback(struct k_work *work) {
|
||||
zmk_hid_indicators_t indicators = hid_indicators;
|
||||
for (int i = 0; i < ZMK_SPLIT_BLE_PERIPHERAL_COUNT; i++) {
|
||||
if (peripherals[i].state != PERIPHERAL_SLOT_STATE_CONNECTED) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (peripherals[i].update_hid_indicators == 0) {
|
||||
// It appears that sometimes the peripheral is considered connected
|
||||
// before the GATT characteristics have been discovered. If this is
|
||||
// the case, the update_hid_indicators handle will not yet be set.
|
||||
continue;
|
||||
}
|
||||
|
||||
int err = bt_gatt_write_without_response(peripherals[i].conn,
|
||||
peripherals[i].update_hid_indicators, &indicators,
|
||||
sizeof(indicators), true);
|
||||
|
||||
if (err) {
|
||||
LOG_ERR("Failed to write HID indicator characteristic (err %d)", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static K_WORK_DEFINE(split_central_update_indicators, split_central_update_indicators_callback);
|
||||
|
||||
int zmk_split_bt_update_hid_indicator(zmk_hid_indicators_t indicators) {
|
||||
hid_indicators = indicators;
|
||||
return k_work_submit_to_queue(&split_central_split_run_q, &split_central_update_indicators);
|
||||
}
|
||||
|
||||
#endif // IS_ENABLED(CONFIG_ZMK_SPLIT_PERIPHERAL_HID_INDICATORS)
|
||||
|
||||
static int finish_init() {
|
||||
return IS_ENABLED(CONFIG_ZMK_BLE_CLEAR_BONDS_ON_START) ? 0 : start_scanning();
|
||||
}
|
||||
@@ -1185,4 +1152,55 @@ static int zmk_split_bt_central_listener_cb(const zmk_event_t *eh) {
|
||||
}
|
||||
|
||||
ZMK_LISTENER(zmk_split_bt_central, zmk_split_bt_central_listener_cb);
|
||||
ZMK_SUBSCRIPTION(zmk_split_bt_central, zmk_physical_layout_selection_changed);
|
||||
ZMK_SUBSCRIPTION(zmk_split_bt_central, zmk_physical_layout_selection_changed);
|
||||
|
||||
static int split_central_bt_send_command(uint8_t source,
|
||||
struct zmk_split_transport_central_command cmd) {
|
||||
if (source >= ARRAY_SIZE(peripherals)) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
switch (cmd.type) {
|
||||
case ZMK_SPLIT_TRANSPORT_CENTRAL_CMD_TYPE_SET_HID_INDICATORS:
|
||||
case ZMK_SPLIT_TRANSPORT_CENTRAL_CMD_TYPE_SET_PHYSICAL_LAYOUT:
|
||||
case ZMK_SPLIT_TRANSPORT_CENTRAL_CMD_TYPE_INVOKE_BEHAVIOR: {
|
||||
struct central_cmd_wrapper wrapper = {.source = source, .cmd = cmd};
|
||||
return split_bt_invoke_behavior_payload(wrapper);
|
||||
}
|
||||
case ZMK_SPLIT_TRANSPORT_CENTRAL_CMD_TYPE_POLL_EVENTS:
|
||||
return -ENOTSUP;
|
||||
default:
|
||||
return -ENOTSUP;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int split_central_bt_get_available_source_ids(uint8_t *sources) {
|
||||
int count = 0;
|
||||
for (int i = 0; i < ZMK_SPLIT_BLE_PERIPHERAL_COUNT; i++) {
|
||||
if (peripherals[i].state != PERIPHERAL_SLOT_STATE_CONNECTED) {
|
||||
continue;
|
||||
}
|
||||
|
||||
sources[count++] = i;
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
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,
|
||||
};
|
||||
|
||||
ZMK_SPLIT_TRANSPORT_CENTRAL_REGISTER(bt_central, ¢ral_api);
|
||||
|
||||
void peripheral_event_work_callback(struct k_work *work) {
|
||||
struct peripheral_event_wrapper ev;
|
||||
while (k_msgq_get(&peripheral_event_msgq, &ev, K_NO_WAIT) == 0) {
|
||||
LOG_DBG("Trigger key position state change for %d",
|
||||
ev.event.data.key_position_event.position);
|
||||
zmk_split_transport_central_peripheral_event_handler(&bt_central, ev.source, ev.event);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);
|
||||
#include <zmk/event_manager.h>
|
||||
#include <zmk/battery.h>
|
||||
#include <zmk/events/battery_state_changed.h>
|
||||
#include <zmk/split/bluetooth/central.h>
|
||||
#include <zmk/split/central.h>
|
||||
|
||||
static void blvl_ccc_cfg_changed(const struct bt_gatt_attr *attr, uint16_t value) {
|
||||
ARG_UNUSED(attr);
|
||||
@@ -32,7 +32,7 @@ static ssize_t read_blvl(struct bt_conn *conn, const struct bt_gatt_attr *attr,
|
||||
uint16_t len, uint16_t offset) {
|
||||
const uint8_t source = (uint8_t)(uint32_t)attr->user_data;
|
||||
uint8_t level = 0;
|
||||
int rc = zmk_split_get_peripheral_battery_level(source, &level);
|
||||
int rc = zmk_split_central_get_peripheral_battery_level(source, &level);
|
||||
|
||||
if (rc == -EINVAL) {
|
||||
LOG_ERR("Invalid peripheral index requested for battery level read: %d", source);
|
||||
|
||||
@@ -18,9 +18,11 @@ LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);
|
||||
#include <zephyr/bluetooth/uuid.h>
|
||||
|
||||
#include <drivers/behavior.h>
|
||||
#include <zmk/stdlib.h>
|
||||
#include <zmk/behavior.h>
|
||||
#include <zmk/matrix.h>
|
||||
#include <zmk/physical_layouts.h>
|
||||
#include <zmk/split/transport/peripheral.h>
|
||||
#include <zmk/split/bluetooth/uuid.h>
|
||||
#include <zmk/split/bluetooth/service.h>
|
||||
|
||||
@@ -60,48 +62,7 @@ static ssize_t split_svc_pos_state(struct bt_conn *conn, const struct bt_gatt_at
|
||||
|
||||
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) {
|
||||
struct zmk_split_run_behavior_payload *payload = attrs->user_data;
|
||||
uint16_t end_addr = offset + len;
|
||||
|
||||
LOG_DBG("offset %d len %d", offset, len);
|
||||
|
||||
if (end_addr > sizeof(struct zmk_split_run_behavior_payload)) {
|
||||
return BT_GATT_ERR(BT_ATT_ERR_INVALID_OFFSET);
|
||||
}
|
||||
|
||||
memcpy(payload + offset, buf, len);
|
||||
|
||||
// We run if:
|
||||
// 1: We've gotten all the position/state/param data.
|
||||
// 2: We have a null terminated string for the behavior device label.
|
||||
const size_t behavior_dev_offset =
|
||||
offsetof(struct zmk_split_run_behavior_payload, behavior_dev);
|
||||
if ((end_addr > sizeof(struct zmk_split_run_behavior_data)) &&
|
||||
payload->behavior_dev[end_addr - behavior_dev_offset - 1] == '\0') {
|
||||
struct zmk_behavior_binding binding = {
|
||||
.param1 = payload->data.param1,
|
||||
.param2 = payload->data.param2,
|
||||
.behavior_dev = payload->behavior_dev,
|
||||
};
|
||||
LOG_DBG("%s with params %d %d: pressed? %d", binding.behavior_dev, binding.param1,
|
||||
binding.param2, payload->data.state);
|
||||
struct zmk_behavior_binding_event event = {.position = payload->data.position,
|
||||
.timestamp = k_uptime_get()};
|
||||
int err;
|
||||
if (payload->data.state > 0) {
|
||||
err = behavior_keymap_binding_pressed(&binding, event);
|
||||
} else {
|
||||
err = behavior_keymap_binding_released(&binding, event);
|
||||
}
|
||||
|
||||
if (err) {
|
||||
LOG_ERR("Failed to invoke behavior %s: %d", binding.behavior_dev, err);
|
||||
}
|
||||
}
|
||||
|
||||
return len;
|
||||
}
|
||||
uint8_t flags);
|
||||
|
||||
static ssize_t split_svc_num_of_positions(struct bt_conn *conn, const struct bt_gatt_attr *attrs,
|
||||
void *buf, uint16_t len, uint16_t offset) {
|
||||
@@ -285,12 +246,12 @@ int send_position_state() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int zmk_split_bt_position_pressed(uint8_t position) {
|
||||
static int zmk_split_bt_position_pressed(uint8_t position) {
|
||||
WRITE_BIT(position_state[position / 8], position % 8, true);
|
||||
return send_position_state();
|
||||
}
|
||||
|
||||
int zmk_split_bt_position_released(uint8_t position) {
|
||||
static int zmk_split_bt_position_released(uint8_t position) {
|
||||
WRITE_BIT(position_state[position / 8], position % 8, false);
|
||||
return send_position_state();
|
||||
}
|
||||
@@ -332,9 +293,9 @@ int send_sensor_state(struct sensor_event ev) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int zmk_split_bt_sensor_triggered(uint8_t sensor_index,
|
||||
const struct zmk_sensor_channel_data channel_data[],
|
||||
size_t channel_data_size) {
|
||||
static int zmk_split_bt_sensor_triggered(uint8_t sensor_index,
|
||||
const struct zmk_sensor_channel_data channel_data[],
|
||||
size_t channel_data_size) {
|
||||
if (channel_data_size > ZMK_SENSOR_EVENT_MAX_CHANNELS) {
|
||||
return -EINVAL;
|
||||
}
|
||||
@@ -349,7 +310,8 @@ int zmk_split_bt_sensor_triggered(uint8_t sensor_index,
|
||||
|
||||
#if IS_ENABLED(CONFIG_ZMK_INPUT_SPLIT)
|
||||
|
||||
int zmk_split_bt_report_input(uint8_t reg, uint8_t type, uint16_t code, int32_t value, bool sync) {
|
||||
static int zmk_split_bt_report_input(uint8_t reg, uint8_t type, uint16_t code, int32_t value,
|
||||
bool sync) {
|
||||
|
||||
for (size_t i = 0; i < split_svc.attr_count; i++) {
|
||||
if (bt_uuid_cmp(split_svc.attrs[i].uuid,
|
||||
@@ -380,3 +342,98 @@ 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) {
|
||||
switch (ev->type) {
|
||||
case ZMK_SPLIT_TRANSPORT_PERIPHERAL_EVENT_TYPE_KEY_POSITION_EVENT:
|
||||
if (ev->data.key_position_event.pressed) {
|
||||
zmk_split_bt_position_pressed(ev->data.key_position_event.position);
|
||||
} else {
|
||||
zmk_split_bt_position_released(ev->data.key_position_event.position);
|
||||
}
|
||||
break;
|
||||
#if ZMK_KEYMAP_HAS_SENSORS
|
||||
case ZMK_SPLIT_TRANSPORT_PERIPHERAL_EVENT_TYPE_SENSOR_EVENT:
|
||||
zmk_split_bt_sensor_triggered(ev->data.sensor_event.sensor_index,
|
||||
&ev->data.sensor_event.channel_data, 1);
|
||||
|
||||
break;
|
||||
#endif
|
||||
|
||||
#if IS_ENABLED(CONFIG_ZMK_INPUT_SPLIT)
|
||||
case ZMK_SPLIT_TRANSPORT_PERIPHERAL_EVENT_TYPE_INPUT_EVENT:
|
||||
return zmk_split_bt_report_input(ev->data.input_event.reg, ev->data.input_event.type,
|
||||
ev->data.input_event.code, ev->data.input_event.value,
|
||||
ev->data.input_event.sync);
|
||||
#endif
|
||||
|
||||
#if IS_ENABLED(CONFIG_ZMK_BATTERY_REPORTING)
|
||||
case ZMK_SPLIT_TRANSPORT_PERIPHERAL_EVENT_TYPE_BATTERY_EVENT:
|
||||
// The BLE transport uses standard BAS service for propagation, so just return success here.
|
||||
return 0;
|
||||
#endif
|
||||
default:
|
||||
LOG_WRN("Unhandled event type %d", ev->type);
|
||||
return -ENOTSUP;
|
||||
}
|
||||
|
||||
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) {
|
||||
struct zmk_split_run_behavior_payload *payload = attrs->user_data;
|
||||
uint16_t end_addr = offset + len;
|
||||
|
||||
LOG_DBG("offset %d len %d", offset, len);
|
||||
|
||||
if (end_addr > sizeof(struct zmk_split_run_behavior_payload)) {
|
||||
return BT_GATT_ERR(BT_ATT_ERR_INVALID_OFFSET);
|
||||
}
|
||||
|
||||
memcpy(payload + offset, buf, len);
|
||||
|
||||
// We run if:
|
||||
// 1: We've gotten all the position/state/param data.
|
||||
// 2: We have a null terminated string for the behavior device label.
|
||||
const size_t behavior_dev_offset =
|
||||
offsetof(struct zmk_split_run_behavior_payload, behavior_dev);
|
||||
if ((end_addr > sizeof(struct zmk_split_run_behavior_data)) &&
|
||||
payload->behavior_dev[end_addr - behavior_dev_offset - 1] == '\0') {
|
||||
|
||||
struct zmk_split_transport_central_command cmd = {
|
||||
.type = ZMK_SPLIT_TRANSPORT_CENTRAL_CMD_TYPE_INVOKE_BEHAVIOR,
|
||||
.data = {.invoke_behavior = {
|
||||
.param1 = payload->data.param1,
|
||||
.param2 = payload->data.param2,
|
||||
.position = payload->data.position,
|
||||
.state = payload->data.state,
|
||||
}}};
|
||||
|
||||
const size_t payload_dev_size = sizeof(cmd.data.invoke_behavior.behavior_dev);
|
||||
if (strlcpy(cmd.data.invoke_behavior.behavior_dev, payload->behavior_dev,
|
||||
payload_dev_size) >= payload_dev_size) {
|
||||
LOG_ERR("Truncated behavior label %s to %s before invoking peripheral behavior",
|
||||
payload->behavior_dev, cmd.data.invoke_behavior.behavior_dev);
|
||||
}
|
||||
|
||||
LOG_DBG("%s with params %d %d: pressed? %d", cmd.data.invoke_behavior.behavior_dev,
|
||||
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);
|
||||
|
||||
if (err) {
|
||||
LOG_ERR("Failed to invoke behavior %s: %d", payload->behavior_dev, err);
|
||||
}
|
||||
}
|
||||
|
||||
return len;
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2020 The ZMK Contributors
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include <zephyr/device.h>
|
||||
#include <zephyr/logging/log.h>
|
||||
|
||||
#include <zmk/split/bluetooth/service.h>
|
||||
|
||||
LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);
|
||||
|
||||
#include <zmk/event_manager.h>
|
||||
#include <zmk/events/position_state_changed.h>
|
||||
#include <zmk/events/sensor_event.h>
|
||||
#include <zmk/hid.h>
|
||||
#include <zmk/sensors.h>
|
||||
#include <zmk/endpoints.h>
|
||||
|
||||
int split_listener(const zmk_event_t *eh) {
|
||||
LOG_DBG("");
|
||||
const struct zmk_position_state_changed *pos_ev;
|
||||
if ((pos_ev = as_zmk_position_state_changed(eh)) != NULL) {
|
||||
if (pos_ev->state) {
|
||||
return zmk_split_bt_position_pressed(pos_ev->position);
|
||||
} else {
|
||||
return zmk_split_bt_position_released(pos_ev->position);
|
||||
}
|
||||
}
|
||||
|
||||
#if ZMK_KEYMAP_HAS_SENSORS
|
||||
const struct zmk_sensor_event *sensor_ev;
|
||||
if ((sensor_ev = as_zmk_sensor_event(eh)) != NULL) {
|
||||
return zmk_split_bt_sensor_triggered(sensor_ev->sensor_index, sensor_ev->channel_data,
|
||||
sensor_ev->channel_data_size);
|
||||
}
|
||||
#endif /* ZMK_KEYMAP_HAS_SENSORS */
|
||||
return ZMK_EV_EVENT_BUBBLE;
|
||||
}
|
||||
|
||||
ZMK_LISTENER(split_listener, split_listener);
|
||||
ZMK_SUBSCRIPTION(split_listener, zmk_position_state_changed);
|
||||
|
||||
#if ZMK_KEYMAP_HAS_SENSORS
|
||||
ZMK_SUBSCRIPTION(split_listener, zmk_sensor_event);
|
||||
#endif /* ZMK_KEYMAP_HAS_SENSORS */
|
||||
Reference in New Issue
Block a user