diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt index cc38244a4..8d3f9bb46 100644 --- a/app/CMakeLists.txt +++ b/app/CMakeLists.txt @@ -97,6 +97,7 @@ target_sources_ifdef(CONFIG_ZMK_BATTERY_REPORTING app PRIVATE src/battery.c) target_sources_ifdef(CONFIG_ZMK_HID_INDICATORS app PRIVATE src/events/hid_indicators_changed.c) target_sources_ifdef(CONFIG_ZMK_SPLIT app PRIVATE src/events/split_peripheral_status_changed.c) +target_sources_ifdef(CONFIG_ZMK_SPLIT app PRIVATE src/events/split_peripheral_layer_changed.c) add_subdirectory_ifdef(CONFIG_ZMK_SPLIT src/split) target_sources_ifdef(CONFIG_USB_DEVICE_STACK app PRIVATE src/usb.c) diff --git a/app/include/zmk/events/split_peripheral_layer_changed.h b/app/include/zmk/events/split_peripheral_layer_changed.h new file mode 100644 index 000000000..2445c164f --- /dev/null +++ b/app/include/zmk/events/split_peripheral_layer_changed.h @@ -0,0 +1,16 @@ +/* + * Copyright (c) 2022 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#pragma once + +#include +#include + +struct zmk_split_peripheral_layer_changed { + uint32_t layers; +}; + +ZMK_EVENT_DECLARE(zmk_split_peripheral_layer_changed); diff --git a/app/include/zmk/split/bluetooth/uuid.h b/app/include/zmk/split/bluetooth/uuid.h index c9a63efa7..6380e08f2 100644 --- a/app/include/zmk/split/bluetooth/uuid.h +++ b/app/include/zmk/split/bluetooth/uuid.h @@ -20,3 +20,4 @@ #define ZMK_SPLIT_BT_UPDATE_HID_INDICATORS_UUID ZMK_BT_SPLIT_UUID(0x00000004) #define ZMK_SPLIT_BT_SELECT_PHYS_LAYOUT_UUID ZMK_BT_SPLIT_UUID(0x00000005) #define ZMK_SPLIT_BT_INPUT_EVENT_UUID ZMK_BT_SPLIT_UUID(0x00000006) +#define ZMK_SPLIT_BT_UPDATE_LAYERS_UUID ZMK_BT_SPLIT_UUID(0x00000007) diff --git a/app/include/zmk/split/central.h b/app/include/zmk/split/central.h index ff971bfc6..3fcf17f23 100644 --- a/app/include/zmk/split/central.h +++ b/app/include/zmk/split/central.h @@ -46,3 +46,5 @@ int zmk_split_central_update_hid_indicator(zmk_hid_indicators_t indicators); int zmk_split_central_get_peripheral_battery_level(uint8_t source, uint8_t *level); #endif // IS_ENABLED(CONFIG_ZMK_SPLIT_BLE_CENTRAL_BATTERY_LEVEL_FETCHING) + +int zmk_split_central_update_layers(uint32_t layers); diff --git a/app/include/zmk/split/peripheral_layers.h b/app/include/zmk/split/peripheral_layers.h new file mode 100644 index 000000000..974a322c9 --- /dev/null +++ b/app/include/zmk/split/peripheral_layers.h @@ -0,0 +1,5 @@ +#pragma once + +void set_peripheral_layers_state(uint32_t new_layers); +bool peripheral_layer_active(uint8_t layer); +uint8_t peripheral_highest_layer_active(void); \ No newline at end of file diff --git a/app/include/zmk/split/transport/types.h b/app/include/zmk/split/transport/types.h index 1d6eb734c..1ce264673 100644 --- a/app/include/zmk/split/transport/types.h +++ b/app/include/zmk/split/transport/types.h @@ -66,6 +66,7 @@ enum zmk_split_transport_central_command_type { ZMK_SPLIT_TRANSPORT_CENTRAL_CMD_TYPE_INVOKE_BEHAVIOR, ZMK_SPLIT_TRANSPORT_CENTRAL_CMD_TYPE_SET_PHYSICAL_LAYOUT, ZMK_SPLIT_TRANSPORT_CENTRAL_CMD_TYPE_SET_HID_INDICATORS, + ZMK_SPLIT_TRANSPORT_CENTRAL_CMD_TYPE_SET_RGB_LAYERS, } __packed; struct zmk_split_transport_central_command { @@ -87,5 +88,9 @@ struct zmk_split_transport_central_command { struct { zmk_hid_indicators_t indicators; } set_hid_indicators; + + struct { + uint32_t layers; + } set_rgb_layers; } data; } __packed; \ No newline at end of file diff --git a/app/src/events/split_peripheral_layer_changed.c b/app/src/events/split_peripheral_layer_changed.c new file mode 100644 index 000000000..81f2ab8de --- /dev/null +++ b/app/src/events/split_peripheral_layer_changed.c @@ -0,0 +1,10 @@ +/* + * Copyright (c) 2022 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#include +#include + +ZMK_EVENT_IMPL(zmk_split_peripheral_layer_changed); \ No newline at end of file diff --git a/app/src/split/CMakeLists.txt b/app/src/split/CMakeLists.txt index 20c730892..2d6ee8e1f 100644 --- a/app/src/split/CMakeLists.txt +++ b/app/src/split/CMakeLists.txt @@ -14,5 +14,6 @@ if (CONFIG_ZMK_SPLIT_ROLE_CENTRAL) zephyr_linker_sources(SECTIONS ../../include/linker/zmk-split-transport-central.ld) else() target_sources(app PRIVATE peripheral.c) + target_sources(app PRIVATE peripheral_layers.c) zephyr_linker_sources(SECTIONS ../../include/linker/zmk-split-transport-peripheral.ld) endif() \ No newline at end of file diff --git a/app/src/split/bluetooth/central.c b/app/src/split/bluetooth/central.c index b4b61ce77..a98e47d4f 100644 --- a/app/src/split/bluetooth/central.c +++ b/app/src/split/bluetooth/central.c @@ -33,6 +33,7 @@ LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); #include #include #include +#include static int start_scanning(void); @@ -60,6 +61,8 @@ struct peripheral_slot { uint16_t update_hid_indicators; #endif // IS_ENABLED(CONFIG_ZMK_SPLIT_PERIPHERAL_HID_INDICATORS) uint16_t selected_physical_layout_handle; + uint16_t update_layers_handle; + uint8_t position_state[POSITION_STATE_DATA_LEN]; uint8_t changed_positions[POSITION_STATE_DATA_LEN]; }; @@ -219,6 +222,7 @@ int release_peripheral_slot(int index) { #if IS_ENABLED(CONFIG_ZMK_SPLIT_PERIPHERAL_HID_INDICATORS) slot->update_hid_indicators = 0; #endif // IS_ENABLED(CONFIG_ZMK_SPLIT_PERIPHERAL_HID_INDICATORS) + slot->update_layers_handle = 0; return 0; } @@ -620,6 +624,10 @@ static uint8_t split_central_chrc_discovery_func(struct bt_conn *conn, LOG_DBG("Found update HID indicators handle"); slot->update_hid_indicators = bt_gatt_attr_value_handle(attr); #endif // IS_ENABLED(CONFIG_ZMK_SPLIT_PERIPHERAL_HID_INDICATORS) + } else if (!bt_uuid_cmp(((struct bt_gatt_chrc *)attr->user_data)->uuid, + BT_UUID_DECLARE_128(ZMK_SPLIT_BT_UPDATE_LAYERS_UUID))) { + LOG_DBG("Found update Layers handle"); + slot->update_layers_handle = bt_gatt_attr_value_handle(attr); #if IS_ENABLED(CONFIG_ZMK_SPLIT_BLE_CENTRAL_BATTERY_LEVEL_FETCHING) } else if (!bt_uuid_cmp(((struct bt_gatt_chrc *)attr->user_data)->uuid, BT_UUID_BAS_BATTERY_LEVEL)) { @@ -707,6 +715,8 @@ static uint8_t split_central_chrc_discovery_func(struct bt_conn *conn, } #endif // IS_ENABLED(CONFIG_ZMK_INPUT_SPLIT) + subscribed = subscribed && slot->update_layers_handle; + return subscribed ? BT_GATT_ITER_STOP : BT_GATT_ITER_CONTINUE; } @@ -1024,6 +1034,7 @@ K_MSGQ_DEFINE(zmk_split_central_split_run_msgq, sizeof(struct central_cmd_wrappe void split_central_split_run_callback(struct k_work *work) { struct central_cmd_wrapper payload_wrapper; + int err; LOG_DBG(""); @@ -1056,7 +1067,7 @@ void split_central_split_run_callback(struct k_work *work) { payload.behavior_dev); } - int err = bt_gatt_write_without_response( + 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); @@ -1082,7 +1093,7 @@ void split_central_split_run_callback(struct k_work *work) { break; } - int err = bt_gatt_write_without_response( + 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, @@ -1093,6 +1104,18 @@ void split_central_split_run_callback(struct k_work *work) { } break; #endif // IS_ENABLED(CONFIG_ZMK_SPLIT_PERIPHERAL_HID_INDICATORS) + case ZMK_SPLIT_TRANSPORT_CENTRAL_CMD_TYPE_SET_RGB_LAYERS: + err = bt_gatt_write_without_response( + peripherals[payload_wrapper.source].conn, + peripherals[payload_wrapper.source].update_layers_handle, + &payload_wrapper.cmd.data.set_rgb_layers.layers, + sizeof(payload_wrapper.cmd.data.set_rgb_layers.layers), true); + + if (err) { + LOG_ERR("Failed to send layers to peripheral (err %d)", err); + } + break; + default: LOG_WRN("Unsupported wrapped central command type %d", payload_wrapper.cmd.type); return; @@ -1174,6 +1197,7 @@ static int split_central_bt_send_command(uint8_t source, } switch (cmd.type) { + case ZMK_SPLIT_TRANSPORT_CENTRAL_CMD_TYPE_SET_RGB_LAYERS: 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: { diff --git a/app/src/split/bluetooth/service.c b/app/src/split/bluetooth/service.c index 5bbed1373..e83a6ea04 100644 --- a/app/src/split/bluetooth/service.c +++ b/app/src/split/bluetooth/service.c @@ -31,9 +31,11 @@ LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); #if IS_ENABLED(CONFIG_ZMK_SPLIT_PERIPHERAL_HID_INDICATORS) #include #endif // IS_ENABLED(CONFIG_ZMK_SPLIT_PERIPHERAL_HID_INDICATORS) +#include #include #include +#include #if ZMK_KEYMAP_HAS_SENSORS static struct sensor_event last_sensor_event; @@ -139,6 +141,31 @@ static ssize_t split_svc_get_selected_phys_layout(struct bt_conn *conn, return bt_gatt_attr_read(conn, attrs, buf, len, offset, &selected, sizeof(selected)); } +static uint32_t layers = 0; + +static void split_svc_update_layers_callback(struct k_work *work) { + LOG_DBG("Setting peripheral layers: %x", layers); + // set_peripheral_layers_state(layers); + raise_zmk_split_peripheral_layer_changed( + (struct zmk_split_peripheral_layer_changed){.layers = layers}); +} + +static K_WORK_DEFINE(split_svc_update_layers_work, split_svc_update_layers_callback); + +static ssize_t split_svc_update_layers(struct bt_conn *conn, const struct bt_gatt_attr *attr, + const void *buf, uint16_t len, uint16_t offset, + uint8_t flags) { + if (offset + len > sizeof(uint32_t)) { + return BT_GATT_ERR(BT_ATT_ERR_INVALID_OFFSET); + } + + memcpy((uint8_t *)&layers + offset, buf, len); + + k_work_submit(&split_svc_update_layers_work); + + return len; +} + #if IS_ENABLED(CONFIG_ZMK_INPUT_SPLIT) static void split_input_events_ccc(const struct bt_gatt_attr *attr, uint16_t value) { @@ -204,8 +231,11 @@ BT_GATT_SERVICE_DEFINE( BT_GATT_CHARACTERISTIC(BT_UUID_DECLARE_128(ZMK_SPLIT_BT_SELECT_PHYS_LAYOUT_UUID), BT_GATT_CHRC_WRITE | BT_GATT_CHRC_READ, BT_GATT_PERM_WRITE_ENCRYPT | BT_GATT_PERM_READ_ENCRYPT, - split_svc_get_selected_phys_layout, split_svc_select_phys_layout, - NULL), ); + split_svc_get_selected_phys_layout, split_svc_select_phys_layout, NULL), + + BT_GATT_CHARACTERISTIC(BT_UUID_DECLARE_128(ZMK_SPLIT_BT_UPDATE_LAYERS_UUID), + BT_GATT_CHRC_WRITE_WITHOUT_RESP, BT_GATT_PERM_WRITE_ENCRYPT, NULL, + split_svc_update_layers, NULL), ); K_THREAD_STACK_DEFINE(service_q_stack, CONFIG_ZMK_SPLIT_BLE_PERIPHERAL_STACK_SIZE); diff --git a/app/src/split/central.c b/app/src/split/central.c index e2b806437..5503b8527 100644 --- a/app/src/split/central.c +++ b/app/src/split/central.c @@ -150,6 +150,42 @@ int zmk_split_central_update_hid_indicator(zmk_hid_indicators_t indicators) { #endif // IS_ENABLED(CONFIG_ZMK_SPLIT_PERIPHERAL_HID_INDICATORS) +int zmk_split_central_update_layers(uint32_t new_layers) { + if (!active_transport || !active_transport->api || + !active_transport->api->get_available_source_ids || !active_transport->api->send_command) { + return -ENODEV; + } + + uint8_t source_ids[ZMK_SPLIT_CENTRAL_PERIPHERAL_COUNT]; + + int ret = active_transport->api->get_available_source_ids(source_ids); + + if (ret < 0) { + return ret; + } + + struct zmk_split_transport_central_command command = + (struct zmk_split_transport_central_command){ + .type = ZMK_SPLIT_TRANSPORT_CENTRAL_CMD_TYPE_SET_RGB_LAYERS, + .data = + { + .set_rgb_layers = + { + .layers = new_layers, + }, + }, + }; + + for (size_t i = 0; i < ret; i++) { + ret = active_transport->api->send_command(source_ids[i], command); + if (ret < 0) { + return ret; + } + } + + return 0; +} + #if IS_ENABLED(CONFIG_ZMK_SPLIT_BLE_CENTRAL_BATTERY_LEVEL_FETCHING) int zmk_split_central_get_peripheral_battery_level(uint8_t source, uint8_t *level) { diff --git a/app/src/split/peripheral.c b/app/src/split/peripheral.c index b814df619..527e0dac0 100644 --- a/app/src/split/peripheral.c +++ b/app/src/split/peripheral.c @@ -21,6 +21,7 @@ #if IS_ENABLED(CONFIG_ZMK_SPLIT_PERIPHERAL_HID_INDICATORS) #include #endif +#include #include #include @@ -66,6 +67,10 @@ int zmk_split_transport_peripheral_command_handler( .indicators = cmd.data.set_hid_indicators.indicators}); } #endif + case ZMK_SPLIT_TRANSPORT_CENTRAL_CMD_TYPE_SET_RGB_LAYERS: { + return raise_zmk_split_peripheral_layer_changed( + (struct zmk_split_peripheral_layer_changed){.layers = cmd.data.set_rgb_layers.layers}); + } default: LOG_WRN("Unhandled command type %d", cmd.type); return -ENOTSUP; diff --git a/app/src/split/peripheral_layers.c b/app/src/split/peripheral_layers.c new file mode 100644 index 000000000..95de8f8e9 --- /dev/null +++ b/app/src/split/peripheral_layers.c @@ -0,0 +1,25 @@ + +#include +#include + +#include +#include + +static uint32_t peripheral_layers = 0; + +void set_peripheral_layers_state(uint32_t new_layers) { peripheral_layers = new_layers; } + +bool peripheral_layer_active(uint8_t layer) { + return (peripheral_layers & (BIT(layer))) == (BIT(layer)); +}; + +uint8_t peripheral_highest_layer_active(void) { + if (peripheral_layers > 0) { + for (uint8_t layer = ZMK_KEYMAP_LAYERS_LEN - 1; layer > 0; layer--) { + if ((peripheral_layers & (BIT(layer))) == (BIT(layer)) || layer == 0) { + return layer; + } + } + } + return 0; +} \ No newline at end of file diff --git a/app/src/split/wired/central.c b/app/src/split/wired/central.c index 7ba661425..b8431b9fc 100644 --- a/app/src/split/wired/central.c +++ b/app/src/split/wired/central.c @@ -185,6 +185,8 @@ static ssize_t get_payload_data_size(const struct zmk_split_transport_central_co return sizeof(cmd->data.set_physical_layout); case ZMK_SPLIT_TRANSPORT_CENTRAL_CMD_TYPE_SET_HID_INDICATORS: return sizeof(cmd->data.set_hid_indicators); + case ZMK_SPLIT_TRANSPORT_CENTRAL_CMD_TYPE_SET_RGB_LAYERS: + return sizeof(cmd->data.set_rgb_layers); default: return -ENOTSUP; }