diff --git a/app/include/zmk/split/bluetooth/peripheral_layers.h b/app/include/zmk/split/bluetooth/peripheral_layers.h new file mode 100644 index 00000000..e816cad0 --- /dev/null +++ b/app/include/zmk/split/bluetooth/peripheral_layers.h @@ -0,0 +1,4 @@ +#pragma once + +void set_peripheral_layers_state(uint32_t new_layers); +bool peripheral_layer_active(uint8_t layer); \ No newline at end of file diff --git a/app/include/zmk/split/bluetooth/uuid.h b/app/include/zmk/split/bluetooth/uuid.h index c9a63efa..6380e08f 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 ff971bfc..3fcf17f2 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/src/keymap.c b/app/src/keymap.c index 762dd4f4..02c9e392 100644 --- a/app/src/keymap.c +++ b/app/src/keymap.c @@ -17,6 +17,9 @@ LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); #include #include #include +#if IS_ENABLED(CONFIG_ZMK_SPLIT_BLE) +#include +#endif #include #include @@ -155,6 +158,9 @@ static inline int set_layer_state(zmk_keymap_layer_id_t layer_id, bool state) { if (ret < 0) { LOG_WRN("Failed to raise layer state changed (%d)", ret); } +#if IS_ENABLED(CONFIG_ZMK_SPLIT_BLE) + zmk_split_central_update_layers(_zmk_keymap_layer_state); +#endif } return ret; diff --git a/app/src/rgb_underglow.c b/app/src/rgb_underglow.c index e93cda91..f9aba88f 100644 --- a/app/src/rgb_underglow.c +++ b/app/src/rgb_underglow.c @@ -36,6 +36,10 @@ #include #endif +#if !IS_ENABLED(CONFIG_ZMK_SPLIT_ROLE_CENTRAL) +#include +#endif + LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); #if !DT_HAS_CHOSEN(zmk_underglow) @@ -51,6 +55,10 @@ LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); #define SAT_MAX 100 #define BRT_MAX 100 +#define LAYER_GAMING 1 +#define LAYER_LOWER 2 +#define LAYER_NUMERIC 3 + BUILD_ASSERT(CONFIG_ZMK_RGB_UNDERGLOW_BRT_MIN <= CONFIG_ZMK_RGB_UNDERGLOW_BRT_MAX, "ERROR: RGB underglow maximum brightness is less than minimum brightness"); @@ -59,6 +67,7 @@ enum rgb_underglow_effect { UNDERGLOW_EFFECT_BREATHE, UNDERGLOW_EFFECT_SPECTRUM, UNDERGLOW_EFFECT_SWIRL, + UNDERGLOW_EFFECT_LAYER_INDICATORS, UNDERGLOW_EFFECT_NUMBER // Used to track number of underglow effects }; @@ -190,6 +199,8 @@ static void zmk_rgb_underglow_effect_swirl(void) { state.animation_step = state.animation_step % HUE_MAX; } +static bool valdur_layer_active(int layer); + static int zmk_led_generate_status(void); static void zmk_led_write_pixels(void) { @@ -270,14 +281,20 @@ static void zmk_led_write_pixels(void) { #if defined(DT_N_S_underglow_indicators_EXISTS) #define UNDERGLOW_INDICATORS_ENABLED 1 +#define LEFT_HALF #else #define UNDERGLOW_INDICATORS_ENABLED 0 +#define RIGHT_HALF #endif #if !UNDERGLOW_INDICATORS_ENABLED static int zmk_led_generate_status(void) { return 0; } +static bool valdur_layer_active(int layer) { return peripheral_layer_active(layer); } + #else +static bool valdur_layer_active(int layer) { return zmk_keymap_layer_active(layer); } + const uint8_t underglow_layer_state[] = DT_PROP(UNDERGLOW_INDICATORS, layer_state); const uint8_t underglow_ble_state[] = DT_PROP(UNDERGLOW_INDICATORS, ble_state); const uint8_t underglow_bat_lhs[] = DT_PROP(UNDERGLOW_INDICATORS, bat_lhs); @@ -289,6 +306,7 @@ const uint8_t underglow_bat_rhs[] = DT_PROP(UNDERGLOW_INDICATORS, bat_rhs); g : (CONFIG_ZMK_RGB_UNDERGLOW_BRT_MAX * (G)) / 0xff, \ b : (CONFIG_ZMK_RGB_UNDERGLOW_BRT_MAX * (B)) / 0xff \ }) + const struct led_rgb red = HEXRGB(0xff, 0x00, 0x00); const struct led_rgb yellow = HEXRGB(0xff, 0xff, 0x00); const struct led_rgb green = HEXRGB(0x00, 0xff, 0x00); @@ -419,6 +437,129 @@ static int zmk_led_generate_status(void) { } #endif // underglow_indicators exists +static inline struct led_rgb hue_sat(int hue, int sat) { + struct zmk_led_hsb hsb = state.color; + hsb.h = hue; + hsb.s = sat; + return hsb_to_rgb(hsb_scale_min_max(hsb)); +} + +#define MK_GREEN hue_sat(150, 100) +#define MK_RED hue_sat(348, 100) +#define MK_BLUE hue_sat(194, 100) +#define MK_ORANGE hue_sat(20, 100) +#define MK_YELLOW hue_sat(51, 100) +#define MK_PURPLE hue_sat(267, 60) +#define MK_WHITE hue_sat(0, 0); + +/* + MoErgo 40 LEDs + + 34 28 22 16 10 10 16 22 28 34 + 35 29 23 17 11 6 6 11 17 23 29 35 + 36 30 24 18 12 7 7 12 18 24 30 36 + 37 31 25 19 13 8 8 13 19 25 31 37 + 38 32 26 20 14 9 9 14 20 26 32 38 + 39 33 27 21 15 15 21 27 33 39 + 0 1 2 2 1 0 + 3 4 5 5 4 3 +*/ + +static void valdur_indicate_custom_layers(void) { + for (int i = 0; i < STRIP_NUM_PIXELS; i++) { + pixels[i] = (struct led_rgb){r : 0, g : 0, b : 0}; + } + if (valdur_layer_active(LAYER_NUMERIC)) { + struct led_rgb col_green = MK_GREEN; + struct led_rgb col_yellow = MK_YELLOW; + +#ifdef LEFT_HALF + // indicator + pixels[36] = col_green; +#endif + + // numbers + pixels[11] = col_green; + pixels[12] = col_green; + pixels[13] = col_green; +#ifdef RIGHT_HALF + pixels[14] = col_green; +#endif + + pixels[17] = col_green; + pixels[18] = col_green; + pixels[19] = col_green; + + pixels[23] = col_green; + pixels[24] = col_green; + pixels[25] = col_green; +#ifdef LEFT_HALF + pixels[26] = col_green; +#endif + + // operators + pixels[31] = col_yellow; + pixels[32] = col_yellow; + pixels[27] = col_yellow; + + pixels[7] = col_yellow; + pixels[8] = col_yellow; + pixels[9] = col_yellow; + + } else if (valdur_layer_active(LAYER_LOWER)) { + struct led_rgb col_orange = MK_ORANGE; + struct led_rgb col_blue = MK_BLUE; + +#ifdef LEFT_HALF + // indicator + pixels[37] = col_orange; +#endif + + // arrows + pixels[18] = col_orange; + pixels[25] = col_orange; + pixels[19] = col_orange; + pixels[13] = col_orange; + + // // ctrl arrows + // pixels[8] = yellow; + // pixels[31] = yellow; + + // home, end, pgup, pgdn + pixels[7] = col_blue; + pixels[8] = col_blue; + pixels[24] = col_blue; + pixels[12] = col_blue; + } else if (valdur_layer_active(LAYER_GAMING)) { + struct led_rgb col_red = MK_RED; + struct led_rgb col_blue = MK_BLUE; +#ifdef LEFT_HALF + + // indicator + pixels[38] = col_red; + + // wsad + pixels[18] = col_red; + pixels[25] = col_red; + pixels[19] = col_red; + pixels[13] = col_red; + + // enter, backspace, delete + pixels[5] = col_blue; + pixels[27] = col_blue; + pixels[33] = col_blue; +#else + pixels[6] = col_red; +#endif + } else { +#ifdef LEFT_HALF + pixels[6] = MK_PURPLE; +#else + pixels[6] = MK_PURPLE; +#endif + } +} + static void zmk_rgb_underglow_tick(struct k_work *work) { switch (state.current_effect) { case UNDERGLOW_EFFECT_SOLID: @@ -433,6 +574,9 @@ static void zmk_rgb_underglow_tick(struct k_work *work) { case UNDERGLOW_EFFECT_SWIRL: zmk_rgb_underglow_effect_swirl(); break; + case UNDERGLOW_EFFECT_LAYER_INDICATORS: + valdur_indicate_custom_layers(); + break; } zmk_led_write_pixels(); @@ -589,7 +733,6 @@ static void zmk_rgb_underglow_off_handler(struct k_work *work) { for (int i = 0; i < STRIP_NUM_PIXELS; i++) { pixels[i] = (struct led_rgb){r : 0, g : 0, b : 0}; } - zmk_led_write_pixels(); } diff --git a/app/src/split/bluetooth/CMakeLists.txt b/app/src/split/bluetooth/CMakeLists.txt index f4e12a9d..2980eb59 100644 --- a/app/src/split/bluetooth/CMakeLists.txt +++ b/app/src/split/bluetooth/CMakeLists.txt @@ -4,6 +4,8 @@ if (NOT CONFIG_ZMK_SPLIT_ROLE_CENTRAL) target_sources(app PRIVATE service.c) target_sources(app PRIVATE peripheral.c) + target_sources(app PRIVATE peripheral_layers.c) + endif() if (CONFIG_ZMK_SPLIT_ROLE_CENTRAL) target_sources(app PRIVATE central.c) diff --git a/app/src/split/bluetooth/central.c b/app/src/split/bluetooth/central.c index 685deb51..8addfa3f 100644 --- a/app/src/split/bluetooth/central.c +++ b/app/src/split/bluetooth/central.c @@ -60,6 +60,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 +221,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 +623,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 +714,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; } @@ -1145,6 +1154,40 @@ static struct settings_handler ble_central_settings_handler = { #endif // IS_ENABLED(CONFIG_SETTINGS) +static uint32_t layers_for_peripheral = 0; + +static void split_central_update_layers_callback(struct k_work *work) { + uint32_t layers = layers_for_peripheral; + for (int i = 0; i < ZMK_SPLIT_BLE_PERIPHERAL_COUNT; i++) { + if (peripherals[i].state != PERIPHERAL_SLOT_STATE_CONNECTED) { + continue; + } + + if (peripherals[i].update_layers_handle == 0) { + continue; + } + + int err = + bt_gatt_write_without_response(peripherals[i].conn, peripherals[i].update_layers_handle, + &layers, sizeof(layers), true); + + if (err) { + LOG_ERR("Failed to send layers to peripheral (err %d)", err); + } else { + LOG_DBG("Sent Layers over to peripheral"); + } + } +} + +static K_WORK_DEFINE(split_central_update_layers, split_central_update_layers_callback); + +int zmk_split_central_update_layers(uint32_t new_layers) { + layers_for_peripheral = new_layers; + return k_work_submit_to_queue(&split_central_split_run_q, &split_central_update_layers); +} + +// valdur layers done + static int zmk_split_bt_central_init(void) { k_work_queue_start(&split_central_split_run_q, split_central_split_run_q_stack, K_THREAD_STACK_SIZEOF(split_central_split_run_q_stack), diff --git a/app/src/split/bluetooth/peripheral_layers.c b/app/src/split/bluetooth/peripheral_layers.c new file mode 100644 index 00000000..fbbffdd6 --- /dev/null +++ b/app/src/split/bluetooth/peripheral_layers.c @@ -0,0 +1,13 @@ + +#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)); +}; \ No newline at end of file diff --git a/app/src/split/bluetooth/service.c b/app/src/split/bluetooth/service.c index 5bbed137..b51cb88f 100644 --- a/app/src/split/bluetooth/service.c +++ b/app/src/split/bluetooth/service.c @@ -31,6 +31,7 @@ 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 @@ -139,6 +140,29 @@ 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); +} + +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 +228,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);