From 319f03fd010d8f7ba980e0bfe92d83315a290ce8 Mon Sep 17 00:00:00 2001 From: Chris Andreae Date: Sat, 28 May 2022 20:15:51 +0900 Subject: [PATCH 01/25] rgb_underglow: refresh more frequently for smoother RGB underglow --- app/src/rgb_underglow.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/rgb_underglow.c b/app/src/rgb_underglow.c index 3453fb44..d78df1b3 100644 --- a/app/src/rgb_underglow.c +++ b/app/src/rgb_underglow.c @@ -274,7 +274,7 @@ static int zmk_rgb_underglow_init(void) { #endif if (state.on) { - k_timer_start(&underglow_tick, K_NO_WAIT, K_MSEC(50)); + k_timer_start(&underglow_tick, K_NO_WAIT, K_MSEC(25)); } return 0; @@ -312,7 +312,7 @@ int zmk_rgb_underglow_on(void) { state.on = true; state.animation_step = 0; - k_timer_start(&underglow_tick, K_NO_WAIT, K_MSEC(50)); + k_timer_start(&underglow_tick, K_NO_WAIT, K_MSEC(25)); return zmk_rgb_underglow_save_state(); } From 954253c6f56a0d6d88dea488534aea410c2b0f12 Mon Sep 17 00:00:00 2001 From: Donald Gordon Date: Wed, 13 Jul 2022 22:39:44 +1200 Subject: [PATCH 02/25] RGB underglow status support Adds Glove80's status indicator using RGB underglow support. Requires ZMK PR#999 and PR#1243. The underglow status is able to show layer state, battery levels, caps/num/scroll-lock, BLE and USB state. The underglow positions selected for each of these indicators is configured using the new devicetree node zmk,underglow-indicators, which takes an array of integer LED positions for each feature. --- .../bindings/zmk,underglow-indicators.yaml | 35 ++ app/include/dt-bindings/zmk/rgb.h | 2 + app/include/zmk/ble.h | 1 + app/include/zmk/endpoints.h | 2 + app/include/zmk/rgb_underglow.h | 1 + app/src/behaviors/behavior_rgb_underglow.c | 2 + app/src/ble.c | 17 + app/src/endpoints.c | 4 + app/src/rgb_underglow.c | 344 ++++++++++++++++-- 9 files changed, 387 insertions(+), 21 deletions(-) create mode 100644 app/dts/bindings/zmk,underglow-indicators.yaml diff --git a/app/dts/bindings/zmk,underglow-indicators.yaml b/app/dts/bindings/zmk,underglow-indicators.yaml new file mode 100644 index 00000000..d5cdde80 --- /dev/null +++ b/app/dts/bindings/zmk,underglow-indicators.yaml @@ -0,0 +1,35 @@ +# Copyright (c) 2020, The ZMK Contributors +# SPDX-License-Identifier: MIT + +description: Underglow indicators + +compatible: "zmk,underglow-indicators" + +properties: + bat-lhs: + type: array + required: true + bat-rhs: + type: array + required: true + capslock: + type: int + required: true + numlock: + type: int + required: true + scrolllock: + type: int + required: true + layer-state: + type: array + required: true + ble-state: + type: array + required: true + usb-state: + type: int + required: true + output-fallback: + type: int + required: true diff --git a/app/include/dt-bindings/zmk/rgb.h b/app/include/dt-bindings/zmk/rgb.h index c1a80082..657ac2fd 100644 --- a/app/include/dt-bindings/zmk/rgb.h +++ b/app/include/dt-bindings/zmk/rgb.h @@ -19,6 +19,7 @@ #define RGB_EFR_CMD 12 #define RGB_EFS_CMD 13 #define RGB_COLOR_HSB_CMD 14 +#define RGB_STATUS_CMD 15 #define RGB_TOG RGB_TOG_CMD 0 #define RGB_ON RGB_ON_CMD 0 @@ -33,6 +34,7 @@ #define RGB_SPD RGB_SPD_CMD 0 #define RGB_EFF RGB_EFF_CMD 0 #define RGB_EFR RGB_EFR_CMD 0 +#define RGB_STATUS RGB_STATUS_CMD 0 #define RGB_COLOR_HSB_VAL(h, s, v) (((h) << 16) + ((s) << 8) + (v)) #define RGB_COLOR_HSB(h, s, v) RGB_COLOR_HSB_CMD##(RGB_COLOR_HSB_VAL(h, s, v)) #define RGB_COLOR_HSV RGB_COLOR_HSB \ No newline at end of file diff --git a/app/include/zmk/ble.h b/app/include/zmk/ble.h index 92b2107d..c65addde 100644 --- a/app/include/zmk/ble.h +++ b/app/include/zmk/ble.h @@ -39,6 +39,7 @@ bool zmk_ble_profile_is_open(uint8_t index); bool zmk_ble_active_profile_is_open(void); bool zmk_ble_active_profile_is_connected(void); char *zmk_ble_active_profile_name(void); +int8_t zmk_ble_profile_status(uint8_t index); int zmk_ble_unpair_all(void); diff --git a/app/include/zmk/endpoints.h b/app/include/zmk/endpoints.h index a2ef3181..aabdb302 100644 --- a/app/include/zmk/endpoints.h +++ b/app/include/zmk/endpoints.h @@ -68,6 +68,8 @@ int zmk_endpoints_toggle_transport(void); */ struct zmk_endpoint_instance zmk_endpoints_selected(void); +bool zmk_endpoints_preferred_transport_is_active(); + int zmk_endpoints_send_report(uint16_t usage_page); #if IS_ENABLED(CONFIG_ZMK_POINTING) diff --git a/app/include/zmk/rgb_underglow.h b/app/include/zmk/rgb_underglow.h index be0ef252..0c45e1c6 100644 --- a/app/include/zmk/rgb_underglow.h +++ b/app/include/zmk/rgb_underglow.h @@ -27,3 +27,4 @@ int zmk_rgb_underglow_change_sat(int direction); int zmk_rgb_underglow_change_brt(int direction); int zmk_rgb_underglow_change_spd(int direction); int zmk_rgb_underglow_set_hsb(struct zmk_led_hsb color); +int zmk_rgb_underglow_status(void); diff --git a/app/src/behaviors/behavior_rgb_underglow.c b/app/src/behaviors/behavior_rgb_underglow.c index 80cd5182..2576486a 100644 --- a/app/src/behaviors/behavior_rgb_underglow.c +++ b/app/src/behaviors/behavior_rgb_underglow.c @@ -242,6 +242,8 @@ static int on_keymap_binding_pressed(struct zmk_behavior_binding *binding, return zmk_rgb_underglow_set_hsb((struct zmk_led_hsb){.h = (binding->param2 >> 16) & 0xFFFF, .s = (binding->param2 >> 8) & 0xFF, .b = binding->param2 & 0xFF}); + case RGB_STATUS_CMD: + return zmk_rgb_underglow_status(); } return -ENOTSUP; diff --git a/app/src/ble.c b/app/src/ble.c index 2611eee5..f14df787 100644 --- a/app/src/ble.c +++ b/app/src/ble.c @@ -143,6 +143,23 @@ bool zmk_ble_profile_is_connected(uint8_t index) { return info.state == BT_CONN_STATE_CONNECTED; } +int8_t zmk_ble_profile_status(uint8_t index) { + if (index >= ZMK_BLE_PROFILE_COUNT) + return -1; + bt_addr_le_t *addr = &profiles[index].peer; + struct bt_conn *conn; + int result; + if (!bt_addr_le_cmp(addr, BT_ADDR_LE_ANY)) { + result = 0; // disconnected + } else if ((conn = bt_conn_lookup_addr_le(BT_ID_DEFAULT, addr)) == NULL) { + result = 1; // paired + } else { + result = 2; // connected + bt_conn_unref(conn); + } + return result; +} + #define CHECKED_ADV_STOP() \ err = bt_le_adv_stop(); \ advertising_status = ZMK_ADV_NONE; \ diff --git a/app/src/endpoints.c b/app/src/endpoints.c index ae0e5e7f..e96ce796 100644 --- a/app/src/endpoints.c +++ b/app/src/endpoints.c @@ -319,6 +319,10 @@ static struct zmk_endpoint_instance get_selected_instance(void) { return instance; } +bool zmk_endpoints_preferred_transport_is_active(void) { + return preferred_transport == get_selected_transport(); +} + static int zmk_endpoints_init(void) { #if IS_ENABLED(CONFIG_SETTINGS) k_work_init_delayable(&endpoints_save_work, endpoints_save_preferred_work); diff --git a/app/src/rgb_underglow.c b/app/src/rgb_underglow.c index d78df1b3..e93cda91 100644 --- a/app/src/rgb_underglow.c +++ b/app/src/rgb_underglow.c @@ -12,6 +12,13 @@ #include #include +#include +#include +#include +#include +#include +#include + #include #include @@ -20,12 +27,15 @@ #include #include -#include #include #include #include #include +#if IS_ENABLED(CONFIG_ZMK_SPLIT_BLE_CENTRAL_BATTERY_LEVEL_FETCHING) +#include +#endif + LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); #if !DT_HAS_CHOSEN(zmk_underglow) @@ -58,11 +68,14 @@ struct rgb_underglow_state { uint8_t current_effect; uint16_t animation_step; bool on; + bool status_active; + uint16_t status_animation_step; }; static const struct device *led_strip; static struct led_rgb pixels[STRIP_NUM_PIXELS]; +static struct led_rgb status_pixels[STRIP_NUM_PIXELS]; static struct rgb_underglow_state state; @@ -70,6 +83,8 @@ static struct rgb_underglow_state state; static const struct device *const ext_power = DEVICE_DT_GET(DT_INST(0, zmk_ext_power_generic)); #endif +void zmk_rgb_set_ext_power(void); + static struct zmk_led_hsb hsb_scale_min_max(struct zmk_led_hsb hsb) { hsb.b = CONFIG_ZMK_RGB_UNDERGLOW_BRT_MIN + (CONFIG_ZMK_RGB_UNDERGLOW_BRT_MAX - CONFIG_ZMK_RGB_UNDERGLOW_BRT_MIN) * hsb.b / BRT_MAX; @@ -175,6 +190,235 @@ static void zmk_rgb_underglow_effect_swirl(void) { state.animation_step = state.animation_step % HUE_MAX; } +static int zmk_led_generate_status(void); + +static void zmk_led_write_pixels(void) { + static struct led_rgb led_buffer[STRIP_NUM_PIXELS]; + int bat0; + int blend = 0; + int reset_ext_power = 0; + +#if IS_ENABLED(CONFIG_ZMK_BATTERY_REPORTING) + bat0 = zmk_battery_state_of_charge(); +#else + bat0 = 100; +#endif + + if (state.status_active) { + blend = zmk_led_generate_status(); + } + + // fast path: no status indicators, battery level OK + if (blend == 0 && bat0 >= 20) { + led_strip_update_rgb(led_strip, pixels, STRIP_NUM_PIXELS); + return; + } + // battery below minimum charge + if (bat0 < 10) { + memset(pixels, 0, sizeof(struct led_rgb) * STRIP_NUM_PIXELS); + if (state.on) { + int c_power = ext_power_get(ext_power); + if (c_power && !state.status_active) { + // power is on, RGB underglow is on, but battery is too low + state.on = false; + reset_ext_power = true; + } + } + } + + if (blend == 0) { + for (int i = 0; i < STRIP_NUM_PIXELS; i++) { + led_buffer[i] = pixels[i]; + } + } else if (blend >= 256) { + for (int i = 0; i < STRIP_NUM_PIXELS; i++) { + led_buffer[i] = status_pixels[i]; + } + } else if (blend < 256) { + uint16_t blend_l = blend; + uint16_t blend_r = 256 - blend; + for (int i = 0; i < STRIP_NUM_PIXELS; i++) { + led_buffer[i].r = + ((status_pixels[i].r * blend_l) >> 8) + ((pixels[i].r * blend_r) >> 8); + led_buffer[i].g = + ((status_pixels[i].g * blend_l) >> 8) + ((pixels[i].g * blend_r) >> 8); + led_buffer[i].b = + ((status_pixels[i].b * blend_l) >> 8) + ((pixels[i].b * blend_r) >> 8); + } + } + + // battery below 20%, reduce LED brightness + if (bat0 < 20) { + for (int i = 0; i < STRIP_NUM_PIXELS; i++) { + led_buffer[i].r = led_buffer[i].r >> 1; + led_buffer[i].g = led_buffer[i].g >> 1; + led_buffer[i].b = led_buffer[i].b >> 1; + } + } + + int err = led_strip_update_rgb(led_strip, led_buffer, STRIP_NUM_PIXELS); + if (err < 0) { + LOG_ERR("Failed to update the RGB strip (%d)", err); + } + + if (reset_ext_power) { + zmk_rgb_set_ext_power(); + } +} + +#define UNDERGLOW_INDICATORS DT_PATH(underglow_indicators) + +#if defined(DT_N_S_underglow_indicators_EXISTS) +#define UNDERGLOW_INDICATORS_ENABLED 1 +#else +#define UNDERGLOW_INDICATORS_ENABLED 0 +#endif + +#if !UNDERGLOW_INDICATORS_ENABLED +static int zmk_led_generate_status(void) { return 0; } +#else + +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); +const uint8_t underglow_bat_rhs[] = DT_PROP(UNDERGLOW_INDICATORS, bat_rhs); + +#define HEXRGB(R, G, B) \ + ((struct led_rgb){ \ + r : (CONFIG_ZMK_RGB_UNDERGLOW_BRT_MAX * (R)) / 0xff, \ + 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); +const struct led_rgb dull_green = HEXRGB(0x00, 0xff, 0x68); +const struct led_rgb magenta = HEXRGB(0xff, 0x00, 0xff); +const struct led_rgb white = HEXRGB(0xff, 0xff, 0xff); +const struct led_rgb lilac = HEXRGB(0x6b, 0x1f, 0xce); + +static void zmk_led_battery_level(int bat_level, const uint8_t *addresses, size_t addresses_len) { + struct led_rgb bat_colour; + + if (bat_level > 40) { + bat_colour = green; + } else if (bat_level > 20) { + bat_colour = yellow; + } else { + bat_colour = red; + } + + // originally, six levels, 0 .. 100 + + for (int i = 0; i < addresses_len; i++) { + int min_level = (i * 100) / (addresses_len - 1); + if (bat_level >= min_level) { + status_pixels[addresses[i]] = bat_colour; + } + } +} + +static void zmk_led_fill(struct led_rgb color, const uint8_t *addresses, size_t addresses_len) { + for (int i = 0; i < addresses_len; i++) { + status_pixels[addresses[i]] = color; + } +} + +#define ZMK_LED_NUMLOCK_BIT BIT(0) +#define ZMK_LED_CAPSLOCK_BIT BIT(1) +#define ZMK_LED_SCROLLLOCK_BIT BIT(2) + +static int zmk_led_generate_status(void) { + for (int i = 0; i < STRIP_NUM_PIXELS; i++) { + status_pixels[i] = (struct led_rgb){r : 0, g : 0, b : 0}; + } + + // BATTERY STATUS +#if IS_ENABLED(CONFIG_ZMK_BATTERY_REPORTING) + zmk_led_battery_level(zmk_battery_state_of_charge(), underglow_bat_lhs, + DT_PROP_LEN(UNDERGLOW_INDICATORS, bat_lhs)); +#if IS_ENABLED(CONFIG_ZMK_SPLIT_BLE_CENTRAL_BATTERY_LEVEL_FETCHING) + uint8_t peripheral_level = 0; + int rc = zmk_split_central_get_peripheral_battery_level(0, &peripheral_level); + + if (rc == 0) { + zmk_led_battery_level(peripheral_level, underglow_bat_rhs, + DT_PROP_LEN(UNDERGLOW_INDICATORS, bat_rhs)); + } else if (rc == -ENOTCONN) { + zmk_led_fill(red, underglow_bat_rhs, DT_PROP_LEN(UNDERGLOW_INDICATORS, bat_rhs)); + } else if (rc == -EINVAL) { + LOG_ERR("Invalid peripheral index requested for battery level read: 0"); + } +#endif // CONFIG_ZMK_SPLIT_BLE_CENTRAL_BATTERY_LEVEL_FETCHING +#endif // CONFIG_ZMK_BATTERY_REPORTING + + // CAPSLOCK/NUMLOCK/SCROLLOCK STATUS + zmk_hid_indicators_t led_flags = zmk_hid_indicators_get_current_profile(); + + if (led_flags & ZMK_LED_CAPSLOCK_BIT) + status_pixels[DT_PROP(UNDERGLOW_INDICATORS, capslock)] = red; + if (led_flags & ZMK_LED_NUMLOCK_BIT) + status_pixels[DT_PROP(UNDERGLOW_INDICATORS, numlock)] = red; + if (led_flags & ZMK_LED_SCROLLLOCK_BIT) + status_pixels[DT_PROP(UNDERGLOW_INDICATORS, scrolllock)] = red; + + // LAYER STATUS + for (uint8_t i = 0; i < DT_PROP_LEN(UNDERGLOW_INDICATORS, layer_state); i++) { + if (zmk_keymap_layer_active(i)) + status_pixels[underglow_layer_state[i]] = magenta; + } + + struct zmk_endpoint_instance active_endpoint = zmk_endpoints_selected(); + + if (!zmk_endpoints_preferred_transport_is_active()) + status_pixels[DT_PROP(UNDERGLOW_INDICATORS, output_fallback)] = red; + +#if IS_ENABLED(CONFIG_ZMK_BLE) + int active_ble_profile_index = zmk_ble_active_profile_index(); + for (uint8_t i = 0; + i < MIN(ZMK_BLE_PROFILE_COUNT, DT_PROP_LEN(UNDERGLOW_INDICATORS, ble_state)); i++) { + int8_t status = zmk_ble_profile_status(i); + int ble_pixel = underglow_ble_state[i]; + if (status == 2 && active_endpoint.transport == ZMK_TRANSPORT_BLE && + active_ble_profile_index == i) { // connected AND active + status_pixels[ble_pixel] = white; + } else if (status == 2) { // connected + status_pixels[ble_pixel] = dull_green; + } else if (status == 1) { // paired + status_pixels[ble_pixel] = red; + } else if (status == 0) { // unused + status_pixels[ble_pixel] = lilac; + } + } +#endif + + enum zmk_usb_conn_state usb_state = zmk_usb_get_conn_state(); + if (usb_state == ZMK_USB_CONN_HID && + active_endpoint.transport == ZMK_TRANSPORT_USB) { // connected AND active + status_pixels[DT_PROP(UNDERGLOW_INDICATORS, usb_state)] = white; + } else if (usb_state == ZMK_USB_CONN_HID) { // connected + status_pixels[DT_PROP(UNDERGLOW_INDICATORS, usb_state)] = dull_green; + } else if (usb_state == ZMK_USB_CONN_POWERED) { // powered + status_pixels[DT_PROP(UNDERGLOW_INDICATORS, usb_state)] = red; + } else if (usb_state == ZMK_USB_CONN_NONE) { // disconnected + status_pixels[DT_PROP(UNDERGLOW_INDICATORS, usb_state)] = lilac; + } + + int16_t blend = 256; + if (state.status_animation_step < (500 / 25)) { + blend = ((state.status_animation_step * 256) / (500 / 25)); + } else if (state.status_animation_step > (8000 / 25)) { + blend = 256 - (((state.status_animation_step - (8000 / 25)) * 256) / (2000 / 25)); + } + if (blend < 0) + blend = 0; + if (blend > 256) + blend = 256; + + return blend; +} +#endif // underglow_indicators exists + static void zmk_rgb_underglow_tick(struct k_work *work) { switch (state.current_effect) { case UNDERGLOW_EFFECT_SOLID: @@ -191,10 +435,7 @@ static void zmk_rgb_underglow_tick(struct k_work *work) { break; } - int err = led_strip_update_rgb(led_strip, pixels, STRIP_NUM_PIXELS); - if (err < 0) { - LOG_ERR("Failed to update the RGB strip (%d)", err); - } + zmk_led_write_pixels(); } K_WORK_DEFINE(underglow_tick_work, zmk_rgb_underglow_tick); @@ -297,20 +538,47 @@ int zmk_rgb_underglow_get_state(bool *on_off) { return 0; } -int zmk_rgb_underglow_on(void) { - if (!led_strip) - return -ENODEV; - +void zmk_rgb_set_ext_power(void) { #if IS_ENABLED(CONFIG_ZMK_RGB_UNDERGLOW_EXT_POWER) - if (ext_power != NULL) { + if (ext_power == NULL) + return; + int c_power = ext_power_get(ext_power); + if (c_power < 0) { + LOG_ERR("Unable to examine EXT_POWER: %d", c_power); + c_power = 0; + } + int desired_state = state.on || state.status_active; + +#if IS_ENABLED(CONFIG_ZMK_BATTERY_REPORTING) + // force power off, when battery low (<10%) + if (state.on && !state.status_active) { + if (zmk_battery_state_of_charge() < 10) { + desired_state = false; + } + } +#endif // CONFIG_ZMK_BATTERY_REPORTING + + if (desired_state && !c_power) { int rc = ext_power_enable(ext_power); if (rc != 0) { LOG_ERR("Unable to enable EXT_POWER: %d", rc); } + } else if (!desired_state && c_power) { + int rc = ext_power_disable(ext_power); + if (rc != 0) { + LOG_ERR("Unable to disable EXT_POWER: %d", rc); + } } -#endif +#endif // CONFIG_ZMK_RGB_UNDERGLOW_EXT_POWER +} + +int zmk_rgb_underglow_on(void) { + if (!led_strip) + return -ENODEV; state.on = true; + zmk_rgb_set_ext_power(); + state.animation_step = 0; k_timer_start(&underglow_tick, K_NO_WAIT, K_MSEC(25)); @@ -322,7 +590,7 @@ static void zmk_rgb_underglow_off_handler(struct k_work *work) { pixels[i] = (struct led_rgb){r : 0, g : 0, b : 0}; } - led_strip_update_rgb(led_strip, pixels, STRIP_NUM_PIXELS); + zmk_led_write_pixels(); } K_WORK_DEFINE(underglow_off_work, zmk_rgb_underglow_off_handler); @@ -331,19 +599,11 @@ int zmk_rgb_underglow_off(void) { if (!led_strip) return -ENODEV; -#if IS_ENABLED(CONFIG_ZMK_RGB_UNDERGLOW_EXT_POWER) - if (ext_power != NULL) { - int rc = ext_power_disable(ext_power); - if (rc != 0) { - LOG_ERR("Unable to disable EXT_POWER: %d", rc); - } - } -#endif - k_work_submit_to_queue(zmk_workqueue_lowprio_work_q(), &underglow_off_work); k_timer_stop(&underglow_tick); state.on = false; + zmk_rgb_set_ext_power(); return zmk_rgb_underglow_save_state(); } @@ -374,6 +634,48 @@ int zmk_rgb_underglow_toggle(void) { return state.on ? zmk_rgb_underglow_off() : zmk_rgb_underglow_on(); } +static void zmk_led_write_pixels_work(struct k_work *work); +static void zmk_rgb_underglow_status_update(struct k_timer *timer); + +K_WORK_DEFINE(underglow_write_work, zmk_led_write_pixels_work); +K_TIMER_DEFINE(underglow_status_update_timer, zmk_rgb_underglow_status_update, NULL); + +static void zmk_rgb_underglow_status_update(struct k_timer *timer) { + if (!state.status_active) + return; + state.status_animation_step++; + if (state.status_animation_step > (10000 / 25)) { + state.status_active = false; + k_timer_stop(&underglow_status_update_timer); + } + if (!k_work_is_pending(&underglow_write_work)) + k_work_submit(&underglow_write_work); +} + +static void zmk_led_write_pixels_work(struct k_work *work) { + zmk_led_write_pixels(); + if (!state.status_active) { + zmk_rgb_set_ext_power(); + } +} + +int zmk_rgb_underglow_status(void) { + if (!state.status_active) { + state.status_animation_step = 0; + } else { + if (state.status_animation_step > (500 / 25)) { + state.status_animation_step = 500 / 25; + } + } + state.status_active = true; + zmk_led_write_pixels(); + zmk_rgb_set_ext_power(); + + k_timer_start(&underglow_status_update_timer, K_NO_WAIT, K_MSEC(25)); + + return 0; +} + int zmk_rgb_underglow_set_hsb(struct zmk_led_hsb color) { if (color.h > HUE_MAX || color.s > SAT_MAX || color.b > BRT_MAX) { return -ENOTSUP; From e1fc10651c87f761202cb0d5e2e3af517d273d58 Mon Sep 17 00:00:00 2001 From: Donald Gordon Date: Sat, 21 Jan 2023 15:33:21 +1300 Subject: [PATCH 03/25] ext_power: add Kconfig for initial ext_power state Adds a Kconfig setting for the default EXT_POWER status at initialization time. Previously it was always initialized to on if no saved value was present. --- app/Kconfig | 10 ++++++++++ app/src/ext_power_generic.c | 14 +++++++++++--- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/app/Kconfig b/app/Kconfig index 6b4e3509..e3bfb37b 100644 --- a/app/Kconfig +++ b/app/Kconfig @@ -435,6 +435,16 @@ config ZMK_GPIO_KEY_WAKEUP_TRIGGER depends on DT_HAS_ZMK_GPIO_KEY_WAKEUP_TRIGGER_ENABLED && ZMK_PM_SOFT_OFF # Power Management +if ZMK_EXT_POWER + +config ZMK_EXT_POWER_START + bool "Enable external power output by default" + default y + +#ZMK_EXT_POWER +endif + +#Power Management endmenu menu "Combo options" diff --git a/app/src/ext_power_generic.c b/app/src/ext_power_generic.c index 3975e038..facbbc9a 100644 --- a/app/src/ext_power_generic.c +++ b/app/src/ext_power_generic.c @@ -136,7 +136,11 @@ static int ext_power_settings_commit() { data->status = true; k_work_schedule(&ext_power_save_work, K_NO_WAIT); - ext_power_enable(dev); + if (IS_ENABLED(CONFIG_ZMK_EXT_POWER_START)) { + ext_power_enable(dev); + } else { + ext_power_disable(dev); + } } return 0; @@ -162,8 +166,12 @@ static int ext_power_generic_init(const struct device *dev) { k_work_init_delayable(&ext_power_save_work, ext_power_save_state_work); #endif - // Enable by default. We may get disabled again once settings load. - ext_power_enable(dev); + // Set to default state by default. This may change again once settings load. + if (IS_ENABLED(CONFIG_ZMK_EXT_POWER_START)) { + ext_power_enable(dev); + } else { + ext_power_disable(dev); + } if (config->init_delay_ms) { k_msleep(config->init_delay_ms); From 01d07494117e24bfff1f4be2378599adf6676a7c Mon Sep 17 00:00:00 2001 From: darknao Date: Fri, 3 May 2024 14:44:08 +0200 Subject: [PATCH 04/25] valdur's mod --- .../zmk/split/bluetooth/peripheral_layers.h | 4 + app/include/zmk/split/bluetooth/uuid.h | 1 + app/include/zmk/split/central.h | 2 + app/src/keymap.c | 6 + app/src/rgb_underglow.c | 145 +++++++++++++++++- app/src/split/bluetooth/CMakeLists.txt | 2 + app/src/split/bluetooth/central.c | 43 ++++++ app/src/split/bluetooth/peripheral_layers.c | 13 ++ app/src/split/bluetooth/service.c | 31 +++- 9 files changed, 244 insertions(+), 3 deletions(-) create mode 100644 app/include/zmk/split/bluetooth/peripheral_layers.h create mode 100644 app/src/split/bluetooth/peripheral_layers.c 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); From 6f9a411793dfc31af17ff4a48e01e628a94a3065 Mon Sep 17 00:00:00 2001 From: darknao Date: Sun, 5 May 2024 18:52:44 +0200 Subject: [PATCH 05/25] underglow-layer: use devicetree & clean up code --- app/CMakeLists.txt | 1 + app/dts/bindings/zmk,underglow-layer.yaml | 15 ++ app/include/dt-bindings/zmk/rgb_colors.h | 17 +++ app/include/zmk/rgb_underglow_layer.h | 18 +++ app/src/rgb_underglow.c | 158 ++++++---------------- app/src/rgb_underglow_layer.c | 65 +++++++++ 6 files changed, 154 insertions(+), 120 deletions(-) create mode 100644 app/dts/bindings/zmk,underglow-layer.yaml create mode 100644 app/include/dt-bindings/zmk/rgb_colors.h create mode 100644 app/include/zmk/rgb_underglow_layer.h create mode 100644 app/src/rgb_underglow_layer.c diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt index 60c502fc..3d78f066 100644 --- a/app/CMakeLists.txt +++ b/app/CMakeLists.txt @@ -101,6 +101,7 @@ add_subdirectory_ifdef(CONFIG_ZMK_SPLIT src/split) target_sources_ifdef(CONFIG_USB_DEVICE_STACK app PRIVATE src/usb.c) target_sources_ifdef(CONFIG_ZMK_USB app PRIVATE src/usb_hid.c) target_sources_ifdef(CONFIG_ZMK_RGB_UNDERGLOW app PRIVATE src/rgb_underglow.c) +target_sources_ifdef(CONFIG_ZMK_RGB_UNDERGLOW app PRIVATE src/rgb_underglow_layer.c) target_sources_ifdef(CONFIG_ZMK_BACKLIGHT app PRIVATE src/backlight.c) target_sources_ifdef(CONFIG_ZMK_LOW_PRIORITY_WORK_QUEUE app PRIVATE src/workqueue.c) target_sources(app PRIVATE src/main.c) diff --git a/app/dts/bindings/zmk,underglow-layer.yaml b/app/dts/bindings/zmk,underglow-layer.yaml new file mode 100644 index 00000000..0f59c89a --- /dev/null +++ b/app/dts/bindings/zmk,underglow-layer.yaml @@ -0,0 +1,15 @@ +description: | + Allows defining a rgbmap composed of multiple layers + +compatible: "zmk,underglow-layer" + +child-binding: + description: "A layer to be used in a rgbmap" + + properties: + bindings: + type: array + required: true + layer-id: + type: int + required: true diff --git a/app/include/dt-bindings/zmk/rgb_colors.h b/app/include/dt-bindings/zmk/rgb_colors.h new file mode 100644 index 00000000..885c82d2 --- /dev/null +++ b/app/include/dt-bindings/zmk/rgb_colors.h @@ -0,0 +1,17 @@ +/* + * Copyright (c) 2021 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#define GREEN 0x00ff00 +#define RED 0xff0000 +#define BLUE 0x0000ff +#define TEAL 0x008080 +#define ORANGE 0xffa500 +#define YELLOW 0xffff00 +#define GOLD 0xffd700 +#define PURPLE 0x800080 +#define PINK 0xffc0cb +#define WHITE 0xffffff +#define ______ 0x000000 \ No newline at end of file diff --git a/app/include/zmk/rgb_underglow_layer.h b/app/include/zmk/rgb_underglow_layer.h new file mode 100644 index 00000000..032714eb --- /dev/null +++ b/app/include/zmk/rgb_underglow_layer.h @@ -0,0 +1,18 @@ +/* + * Copyright (c) 2020 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#pragma once +#include + + +#define ZMK_RGB_CHILD_LEN_PLUS_ONE(node) 1 + + +#define ZMK_RGBMAP_LAYERS_LEN \ + (DT_FOREACH_CHILD(DT_INST(0, zmk_underglow_layer), ZMK_RGB_CHILD_LEN_PLUS_ONE) 0) + +const int zmk_rgbmap_id(uint8_t layer); +uint32_t *rgb_underglow_get_bindings(void); +uint8_t rgb_underglow_top_layer(void); \ No newline at end of file diff --git a/app/src/rgb_underglow.c b/app/src/rgb_underglow.c index f9aba88f..7fd0d0c3 100644 --- a/app/src/rgb_underglow.c +++ b/app/src/rgb_underglow.c @@ -16,6 +16,8 @@ #include #include #include +#include + #include #include @@ -25,6 +27,7 @@ #include #include +#include #include #include @@ -55,10 +58,6 @@ 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"); @@ -199,8 +198,6 @@ 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) { @@ -289,12 +286,7 @@ static void zmk_led_write_pixels(void) { #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); @@ -444,119 +436,45 @@ static inline struct led_rgb hue_sat(int hue, int 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); +static struct led_rgb hex_to_rgb(uint8_t r, uint8_t g, uint8_t b) { + struct zmk_led_hsb hsb = state.color; + return (struct led_rgb){ + r : (hsb.b * (r)) / 0xff, + g : (hsb.b * (g)) / 0xff, + b : (hsb.b * (b)) / 0xff + }; +} -/* - 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) { +static void zmk_rgb_underglow_apply_rgbmap(uint32_t rgbmap[], size_t rgbmap_len) { +// TODO: Glove80 specifics, move that part to board's devicetree +#ifdef LEFT_HALF + const uint8_t LED_MATRIX[] = {52, 53, 54, 69, 70, 71, 15, 27, 39, 51, 4, 14, 26, 38, + 50, 68, 3, 13, 25, 37, 49, 67, 2, 12, 24, 36, 48, 66, + 1, 11, 23, 35, 47, 65, 0, 10, 22, 34, 46, 64}; +#else + const uint8_t LED_MATRIX[] = {57, 56, 55, 74, 73, 72, 16, 28, 40, 58, 5, 17, 29, 41, + 59, 75, 6, 18, 30, 42, 60, 76, 7, 19, 31, 43, 61, 77, + 8, 20, 32, 44, 62, 78, 9, 21, 33, 45, 63, 79}; +#endif for (int i = 0; i < STRIP_NUM_PIXELS; i++) { - pixels[i] = (struct led_rgb){r : 0, g : 0, b : 0}; + uint8_t midx = LED_MATRIX[i]; + if (midx >= ZMK_KEYMAP_LEN) { + LOG_DBG("out of range"); + } else { + pixels[i] = hex_to_rgb((rgbmap[midx] & 0xFF0000) >> 16, (rgbmap[midx] & 0xFF00) >> 8, + rgbmap[midx] & 0xFF); + } } - 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 +static void zmk_rgb_underglow_set_layer(void) { + uint32_t *rgbmap = rgb_underglow_get_bindings(); + if (rgbmap != NULL) { + zmk_rgb_underglow_apply_rgbmap(rgbmap, ZMK_KEYMAP_LEN); } else { -#ifdef LEFT_HALF - pixels[6] = MK_PURPLE; -#else - pixels[6] = MK_PURPLE; -#endif + for (int i = 0; i < STRIP_NUM_PIXELS; i++) { + pixels[i] = (struct led_rgb){r : 0, g : 0, b : 0}; + } } } @@ -575,7 +493,7 @@ static void zmk_rgb_underglow_tick(struct k_work *work) { zmk_rgb_underglow_effect_swirl(); break; case UNDERGLOW_EFFECT_LAYER_INDICATORS: - valdur_indicate_custom_layers(); + zmk_rgb_underglow_set_layer(); break; } diff --git a/app/src/rgb_underglow_layer.c b/app/src/rgb_underglow_layer.c new file mode 100644 index 00000000..21208b1f --- /dev/null +++ b/app/src/rgb_underglow_layer.c @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2020 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#include +#include +#include +#include +#include +LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); + +#include +#include +#include + +#if !IS_ENABLED(CONFIG_ZMK_SPLIT_ROLE_CENTRAL) +#include +#endif + +#define DT_DRV_COMPAT zmk_underglow_layer + +#define LAYER_ID(node) DT_PROP(node, layer_id) +#define RGB_BINDINGS(node) DT_PROP(node, bindings) + +static uint32_t zmk_rgbmap[ZMK_RGBMAP_LAYERS_LEN][ZMK_KEYMAP_LEN] = { + DT_INST_FOREACH_CHILD_SEP(0, RGB_BINDINGS, (, )) +}; + +static int zmk_rgbmap_ids[ZMK_RGBMAP_LAYERS_LEN] = { + DT_INST_FOREACH_CHILD_SEP(0, LAYER_ID, (, ))}; + + +const int zmk_rgbmap_id(uint8_t layer) { + for (uint8_t i = 0; i < ZMK_RGBMAP_LAYERS_LEN; i++) { + if (zmk_rgbmap_ids[i] == layer) { + return i; + } + } + return -1; +} + +uint32_t *rgb_underglow_get_bindings(void) { + uint8_t layer = rgb_underglow_top_layer(); + int rgblayer = zmk_rgbmap_id(layer); + if (rgblayer == -1){ + return NULL; + } else { + return zmk_rgbmap[rgblayer]; + } +} + +uint8_t rgb_underglow_top_layer(void) { + for (uint8_t layer = ZMK_KEYMAP_LAYERS_LEN - 1; layer > 0; layer--) { +#if IS_ENABLED(CONFIG_ZMK_SPLIT_ROLE_CENTRAL) + if (zmk_keymap_layer_active(layer)) { +#else + if (peripheral_layer_active(layer)) { +#endif + return layer; + } + } + return -1; +} From 0ca086a84593efa2d264b17c2ea42e30d5b1ad5f Mon Sep 17 00:00:00 2001 From: darknao Date: Wed, 8 May 2024 02:07:54 +0200 Subject: [PATCH 06/25] underglow-layer: track layer changes with event & battery life optimization I tried using event instead of the 25ms underglow_tick to update the underglow on layer change only. That didn't improve the battery life much.... The second change is cutting off the led strip power if the underglow is not defined for a layer. Power is restored if a layer with rgb is activated, and cut off as soon as the layer is disabled. This, on the other hand, improves the battery life a lot, especially if you don't use rgb on your base layer. If you are using rgb on your base layer, setting CONFIG_ZMK_RGB_UNDERGLOW_AUTO_OFF_IDLE is highly recommended. --- app/CMakeLists.txt | 1 + .../events/split_peripheral_layer_changed.h | 16 +++++++++++++ app/include/zmk/rgb_underglow_layer.h | 4 ++-- .../events/split_peripheral_layer_changed.c | 10 ++++++++ app/src/rgb_underglow.c | 23 +++++++++++++++---- app/src/rgb_underglow_layer.c | 14 ++++------- app/src/split/bluetooth/central.c | 4 ++++ app/src/split/bluetooth/service.c | 5 +++- 8 files changed, 61 insertions(+), 16 deletions(-) create mode 100644 app/include/zmk/events/split_peripheral_layer_changed.h create mode 100644 app/src/events/split_peripheral_layer_changed.c diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt index 3d78f066..f01db605 100644 --- a/app/CMakeLists.txt +++ b/app/CMakeLists.txt @@ -96,6 +96,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 00000000..2445c164 --- /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/rgb_underglow_layer.h b/app/include/zmk/rgb_underglow_layer.h index 032714eb..ad28f74f 100644 --- a/app/include/zmk/rgb_underglow_layer.h +++ b/app/include/zmk/rgb_underglow_layer.h @@ -14,5 +14,5 @@ (DT_FOREACH_CHILD(DT_INST(0, zmk_underglow_layer), ZMK_RGB_CHILD_LEN_PLUS_ONE) 0) const int zmk_rgbmap_id(uint8_t layer); -uint32_t *rgb_underglow_get_bindings(void); -uint8_t rgb_underglow_top_layer(void); \ No newline at end of file +uint32_t *rgb_underglow_get_bindings(uint8_t layer); +uint8_t rgb_underglow_top_layer_with_state(uint32_t state_to_test); \ 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 00000000..81f2ab8d --- /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/rgb_underglow.c b/app/src/rgb_underglow.c index 7fd0d0c3..981d21ae 100644 --- a/app/src/rgb_underglow.c +++ b/app/src/rgb_underglow.c @@ -34,6 +34,7 @@ #include #include #include +#include #if IS_ENABLED(CONFIG_ZMK_SPLIT_BLE_CENTRAL_BATTERY_LEVEL_FETCHING) #include @@ -467,15 +468,19 @@ static void zmk_rgb_underglow_apply_rgbmap(uint32_t rgbmap[], size_t rgbmap_len) } } -static void zmk_rgb_underglow_set_layer(void) { - uint32_t *rgbmap = rgb_underglow_get_bindings(); +static void zmk_rgb_underglow_set_layer(uint8_t layer) { + state.on = true; + uint32_t *rgbmap = rgb_underglow_get_bindings(layer); if (rgbmap != NULL) { zmk_rgb_underglow_apply_rgbmap(rgbmap, ZMK_KEYMAP_LEN); } else { for (int i = 0; i < STRIP_NUM_PIXELS; i++) { pixels[i] = (struct led_rgb){r : 0, g : 0, b : 0}; } + state.on = false; } + zmk_led_write_pixels(); + zmk_rgb_set_ext_power(); } static void zmk_rgb_underglow_tick(struct k_work *work) { @@ -493,7 +498,7 @@ static void zmk_rgb_underglow_tick(struct k_work *work) { zmk_rgb_underglow_effect_swirl(); break; case UNDERGLOW_EFFECT_LAYER_INDICATORS: - zmk_rgb_underglow_set_layer(); + //zmk_rgb_underglow_set_layer(); break; } @@ -503,7 +508,7 @@ static void zmk_rgb_underglow_tick(struct k_work *work) { K_WORK_DEFINE(underglow_tick_work, zmk_rgb_underglow_tick); static void zmk_rgb_underglow_tick_handler(struct k_timer *timer) { - if (!state.on) { + if (!state.on && state.current_effect == UNDERGLOW_EFFECT_LAYER_INDICATORS) { return; } @@ -860,6 +865,15 @@ static int rgb_underglow_event_listener(const zmk_event_t *eh) { if (as_zmk_activity_state_changed(eh)) { return rgb_underglow_auto_state(zmk_activity_get_state() == ZMK_ACTIVITY_ACTIVE); } + if (as_zmk_split_peripheral_layer_changed(eh)) { + const struct zmk_split_peripheral_layer_changed *ev = as_zmk_split_peripheral_layer_changed(eh); + LOG_DBG("zmk_split_peripheral_layer_changed: %08x", ev->layers); + + uint8_t layer = rgb_underglow_top_layer_with_state(ev->layers); + LOG_DBG("top layer: %d", layer); + zmk_rgb_underglow_set_layer(layer); + return 0; + } #endif #if IS_ENABLED(CONFIG_ZMK_RGB_UNDERGLOW_AUTO_OFF_USB) @@ -877,6 +891,7 @@ ZMK_LISTENER(rgb_underglow, rgb_underglow_event_listener); #if IS_ENABLED(CONFIG_ZMK_RGB_UNDERGLOW_AUTO_OFF_IDLE) ZMK_SUBSCRIPTION(rgb_underglow, zmk_activity_state_changed); +ZMK_SUBSCRIPTION(rgb_underglow, zmk_split_peripheral_layer_changed); #endif #if IS_ENABLED(CONFIG_ZMK_RGB_UNDERGLOW_AUTO_OFF_USB) diff --git a/app/src/rgb_underglow_layer.c b/app/src/rgb_underglow_layer.c index 21208b1f..28dfe26b 100644 --- a/app/src/rgb_underglow_layer.c +++ b/app/src/rgb_underglow_layer.c @@ -41,8 +41,7 @@ const int zmk_rgbmap_id(uint8_t layer) { return -1; } -uint32_t *rgb_underglow_get_bindings(void) { - uint8_t layer = rgb_underglow_top_layer(); +uint32_t *rgb_underglow_get_bindings(uint8_t layer) { int rgblayer = zmk_rgbmap_id(layer); if (rgblayer == -1){ return NULL; @@ -51,15 +50,12 @@ uint32_t *rgb_underglow_get_bindings(void) { } } -uint8_t rgb_underglow_top_layer(void) { +uint8_t rgb_underglow_top_layer_with_state(uint32_t state_to_test) { for (uint8_t layer = ZMK_KEYMAP_LAYERS_LEN - 1; layer > 0; layer--) { -#if IS_ENABLED(CONFIG_ZMK_SPLIT_ROLE_CENTRAL) - if (zmk_keymap_layer_active(layer)) { -#else - if (peripheral_layer_active(layer)) { -#endif + if ((state_to_test & (BIT(layer))) == (BIT(layer)) || layer == 0) { return layer; } } - return -1; + // return default layer (0) + return 0; } diff --git a/app/src/split/bluetooth/central.c b/app/src/split/bluetooth/central.c index 8addfa3f..6fdbcbd2 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); @@ -1175,6 +1176,9 @@ static void split_central_update_layers_callback(struct k_work *work) { LOG_ERR("Failed to send layers to peripheral (err %d)", err); } else { LOG_DBG("Sent Layers over to peripheral"); + raise_zmk_split_peripheral_layer_changed( + (struct zmk_split_peripheral_layer_changed){.layers = layers}); + } } } diff --git a/app/src/split/bluetooth/service.c b/app/src/split/bluetooth/service.c index b51cb88f..9f02a4ca 100644 --- a/app/src/split/bluetooth/service.c +++ b/app/src/split/bluetooth/service.c @@ -35,6 +35,7 @@ LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); #include #include +#include #if ZMK_KEYMAP_HAS_SENSORS static struct sensor_event last_sensor_event; @@ -144,7 +145,9 @@ 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); + // 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); From da994be0ba5bd73e3f6682c74cad39ced1556b89 Mon Sep 17 00:00:00 2001 From: darknao Date: Sun, 12 May 2024 23:11:25 +0200 Subject: [PATCH 07/25] underglow-layer: enable only if config exists in devicetree --- app/include/zmk/rgb_underglow_layer.h | 4 +-- .../zmk/split/bluetooth/peripheral_layers.h | 3 +- app/src/rgb_underglow.c | 30 +++++++++++++++---- app/src/rgb_underglow_layer.c | 20 +++++++++---- app/src/split/bluetooth/peripheral_layers.c | 14 ++++++++- 5 files changed, 55 insertions(+), 16 deletions(-) diff --git a/app/include/zmk/rgb_underglow_layer.h b/app/include/zmk/rgb_underglow_layer.h index ad28f74f..3681a97e 100644 --- a/app/include/zmk/rgb_underglow_layer.h +++ b/app/include/zmk/rgb_underglow_layer.h @@ -7,7 +7,6 @@ #pragma once #include - #define ZMK_RGB_CHILD_LEN_PLUS_ONE(node) 1 + #define ZMK_RGBMAP_LAYERS_LEN \ @@ -15,4 +14,5 @@ const int zmk_rgbmap_id(uint8_t layer); uint32_t *rgb_underglow_get_bindings(uint8_t layer); -uint8_t rgb_underglow_top_layer_with_state(uint32_t state_to_test); \ No newline at end of file +uint8_t rgb_underglow_top_layer_with_state(uint32_t state_to_test); +uint8_t rgb_underglow_top_layer(void); \ No newline at end of file diff --git a/app/include/zmk/split/bluetooth/peripheral_layers.h b/app/include/zmk/split/bluetooth/peripheral_layers.h index e816cad0..974a322c 100644 --- a/app/include/zmk/split/bluetooth/peripheral_layers.h +++ b/app/include/zmk/split/bluetooth/peripheral_layers.h @@ -1,4 +1,5 @@ #pragma once void set_peripheral_layers_state(uint32_t new_layers); -bool peripheral_layer_active(uint8_t layer); \ No newline at end of file +bool peripheral_layer_active(uint8_t layer); +uint8_t peripheral_highest_layer_active(void); \ No newline at end of file diff --git a/app/src/rgb_underglow.c b/app/src/rgb_underglow.c index 981d21ae..5f3959a7 100644 --- a/app/src/rgb_underglow.c +++ b/app/src/rgb_underglow.c @@ -52,6 +52,10 @@ LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); #endif +#if DT_HAS_COMPAT_STATUS_OKAY(zmk_underglow_layer) +#define UNDERGLOW_LAYER_ENABLED +#endif + #define STRIP_CHOSEN DT_CHOSEN(zmk_underglow) #define STRIP_NUM_PIXELS DT_PROP(STRIP_CHOSEN, chain_length) @@ -437,6 +441,8 @@ static inline struct led_rgb hue_sat(int hue, int sat) { return hsb_to_rgb(hsb_scale_min_max(hsb)); } +#ifdef UNDERGLOW_LAYER_ENABLED + static struct led_rgb hex_to_rgb(uint8_t r, uint8_t g, uint8_t b) { struct zmk_led_hsb hsb = state.color; return (struct led_rgb){ @@ -482,6 +488,7 @@ static void zmk_rgb_underglow_set_layer(uint8_t layer) { zmk_led_write_pixels(); zmk_rgb_set_ext_power(); } +#endif /* UNDERGLOW_LAYER_ENABLED */ static void zmk_rgb_underglow_tick(struct k_work *work) { switch (state.current_effect) { @@ -498,7 +505,7 @@ static void zmk_rgb_underglow_tick(struct k_work *work) { zmk_rgb_underglow_effect_swirl(); break; case UNDERGLOW_EFFECT_LAYER_INDICATORS: - //zmk_rgb_underglow_set_layer(); + // zmk_rgb_underglow_set_layer(); break; } @@ -849,6 +856,9 @@ static int rgb_underglow_auto_state(bool target_wake_state) { if (sleep_state.is_awake) { if (sleep_state.rgb_state_before_sleeping) { +#ifdef UNDERGLOW_LAYER_ENABLED + zmk_rgb_underglow_set_layer(rgb_underglow_top_layer()); +#endif return zmk_rgb_underglow_on(); } else { return zmk_rgb_underglow_off(); @@ -865,16 +875,22 @@ static int rgb_underglow_event_listener(const zmk_event_t *eh) { if (as_zmk_activity_state_changed(eh)) { return rgb_underglow_auto_state(zmk_activity_get_state() == ZMK_ACTIVITY_ACTIVE); } - if (as_zmk_split_peripheral_layer_changed(eh)) { - const struct zmk_split_peripheral_layer_changed *ev = as_zmk_split_peripheral_layer_changed(eh); - LOG_DBG("zmk_split_peripheral_layer_changed: %08x", ev->layers); +#endif - uint8_t layer = rgb_underglow_top_layer_with_state(ev->layers); +#ifdef UNDERGLOW_LAYER_ENABLED + if (as_zmk_split_peripheral_layer_changed(eh)) { + const struct zmk_split_peripheral_layer_changed *ev = + as_zmk_split_peripheral_layer_changed(eh); + LOG_DBG("zmk_split_peripheral_layer_changed: %08x", ev->layers); +#if !IS_ENABLED(CONFIG_ZMK_SPLIT_ROLE_CENTRAL) + set_peripheral_layers_state(ev->layers); +#endif + uint8_t layer = rgb_underglow_top_layer(); LOG_DBG("top layer: %d", layer); zmk_rgb_underglow_set_layer(layer); return 0; } -#endif +#endif /* UNDERGLOW_LAYER_ENABLED */ #if IS_ENABLED(CONFIG_ZMK_RGB_UNDERGLOW_AUTO_OFF_USB) if (as_zmk_usb_conn_state_changed(eh)) { @@ -891,8 +907,10 @@ ZMK_LISTENER(rgb_underglow, rgb_underglow_event_listener); #if IS_ENABLED(CONFIG_ZMK_RGB_UNDERGLOW_AUTO_OFF_IDLE) ZMK_SUBSCRIPTION(rgb_underglow, zmk_activity_state_changed); +#ifdef UNDERGLOW_LAYER_ENABLED ZMK_SUBSCRIPTION(rgb_underglow, zmk_split_peripheral_layer_changed); #endif +#endif #if IS_ENABLED(CONFIG_ZMK_RGB_UNDERGLOW_AUTO_OFF_USB) ZMK_SUBSCRIPTION(rgb_underglow, zmk_usb_conn_state_changed); diff --git a/app/src/rgb_underglow_layer.c b/app/src/rgb_underglow_layer.c index 28dfe26b..fb9876cf 100644 --- a/app/src/rgb_underglow_layer.c +++ b/app/src/rgb_underglow_layer.c @@ -20,17 +20,16 @@ LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); #endif #define DT_DRV_COMPAT zmk_underglow_layer +#if DT_HAS_COMPAT_STATUS_OKAY(DT_DRV_COMPAT) +#define UNDERGLOW_LAYER_ENABLED #define LAYER_ID(node) DT_PROP(node, layer_id) #define RGB_BINDINGS(node) DT_PROP(node, bindings) static uint32_t zmk_rgbmap[ZMK_RGBMAP_LAYERS_LEN][ZMK_KEYMAP_LEN] = { - DT_INST_FOREACH_CHILD_SEP(0, RGB_BINDINGS, (, )) -}; - -static int zmk_rgbmap_ids[ZMK_RGBMAP_LAYERS_LEN] = { - DT_INST_FOREACH_CHILD_SEP(0, LAYER_ID, (, ))}; + DT_INST_FOREACH_CHILD_SEP(0, RGB_BINDINGS, (, ))}; +static int zmk_rgbmap_ids[ZMK_RGBMAP_LAYERS_LEN] = {DT_INST_FOREACH_CHILD_SEP(0, LAYER_ID, (, ))}; const int zmk_rgbmap_id(uint8_t layer) { for (uint8_t i = 0; i < ZMK_RGBMAP_LAYERS_LEN; i++) { @@ -43,7 +42,7 @@ const int zmk_rgbmap_id(uint8_t layer) { uint32_t *rgb_underglow_get_bindings(uint8_t layer) { int rgblayer = zmk_rgbmap_id(layer); - if (rgblayer == -1){ + if (rgblayer == -1) { return NULL; } else { return zmk_rgbmap[rgblayer]; @@ -59,3 +58,12 @@ uint8_t rgb_underglow_top_layer_with_state(uint32_t state_to_test) { // return default layer (0) return 0; } + +uint8_t rgb_underglow_top_layer(void) { +#if IS_ENABLED(CONFIG_ZMK_SPLIT_ROLE_CENTRAL) + return zmk_keymap_highest_layer_active(); +#else + return peripheral_highest_layer_active(); +#endif +} +#endif /* DT_HAS_COMPAT_STATUS_OKAY(DT_DRV_COMPAT) */ \ No newline at end of file diff --git a/app/src/split/bluetooth/peripheral_layers.c b/app/src/split/bluetooth/peripheral_layers.c index fbbffdd6..c5e8c68d 100644 --- a/app/src/split/bluetooth/peripheral_layers.c +++ b/app/src/split/bluetooth/peripheral_layers.c @@ -3,6 +3,7 @@ #include #include +#include static uint32_t peripheral_layers = 0; @@ -10,4 +11,15 @@ void set_peripheral_layers_state(uint32_t new_layers) { peripheral_layers = new_ bool peripheral_layer_active(uint8_t layer) { return (peripheral_layers & (BIT(layer))) == (BIT(layer)); -}; \ No newline at end of file +}; + +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 From 9308ba806a9b808b96ba5eb0273eb640e18a490c Mon Sep 17 00:00:00 2001 From: darknao Date: Mon, 13 May 2024 14:43:08 +0200 Subject: [PATCH 08/25] underglow-layer: Don't save state on idle/resume & register activity on layer change --- app/include/zmk/rgb_underglow.h | 2 ++ app/src/activity.c | 2 ++ app/src/rgb_underglow.c | 43 +++++++++++++++++++++------------ 3 files changed, 31 insertions(+), 16 deletions(-) diff --git a/app/include/zmk/rgb_underglow.h b/app/include/zmk/rgb_underglow.h index 0c45e1c6..f00dcd5e 100644 --- a/app/include/zmk/rgb_underglow.h +++ b/app/include/zmk/rgb_underglow.h @@ -16,6 +16,8 @@ int zmk_rgb_underglow_toggle(void); int zmk_rgb_underglow_get_state(bool *state); int zmk_rgb_underglow_on(void); int zmk_rgb_underglow_off(void); +int zmk_rgb_underglow_transient_on(void); +int zmk_rgb_underglow_transient_off(void); int zmk_rgb_underglow_cycle_effect(int direction); int zmk_rgb_underglow_calc_effect(int direction); int zmk_rgb_underglow_select_effect(int effect); diff --git a/app/src/activity.c b/app/src/activity.c index b109d46d..161771df 100644 --- a/app/src/activity.c +++ b/app/src/activity.c @@ -16,6 +16,7 @@ LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); #include #include #include +#include #include #include @@ -109,6 +110,7 @@ static int activity_init(void) { ZMK_LISTENER(activity, activity_event_listener); ZMK_SUBSCRIPTION(activity, zmk_position_state_changed); ZMK_SUBSCRIPTION(activity, zmk_sensor_event); +ZMK_SUBSCRIPTION(activity, zmk_split_peripheral_layer_changed); #if IS_ENABLED(CONFIG_ZMK_POINTING) diff --git a/app/src/rgb_underglow.c b/app/src/rgb_underglow.c index 5f3959a7..6fa9cf83 100644 --- a/app/src/rgb_underglow.c +++ b/app/src/rgb_underglow.c @@ -475,18 +475,17 @@ static void zmk_rgb_underglow_apply_rgbmap(uint32_t rgbmap[], size_t rgbmap_len) } static void zmk_rgb_underglow_set_layer(uint8_t layer) { - state.on = true; + if (state.current_effect != UNDERGLOW_EFFECT_LAYER_INDICATORS) + return; + uint32_t *rgbmap = rgb_underglow_get_bindings(layer); if (rgbmap != NULL) { zmk_rgb_underglow_apply_rgbmap(rgbmap, ZMK_KEYMAP_LEN); + zmk_rgb_underglow_transient_on(); + zmk_led_write_pixels(); } else { - for (int i = 0; i < STRIP_NUM_PIXELS; i++) { - pixels[i] = (struct led_rgb){r : 0, g : 0, b : 0}; - } - state.on = false; + zmk_rgb_underglow_transient_off(); } - zmk_led_write_pixels(); - zmk_rgb_set_ext_power(); } #endif /* UNDERGLOW_LAYER_ENABLED */ @@ -515,7 +514,7 @@ static void zmk_rgb_underglow_tick(struct k_work *work) { K_WORK_DEFINE(underglow_tick_work, zmk_rgb_underglow_tick); static void zmk_rgb_underglow_tick_handler(struct k_timer *timer) { - if (!state.on && state.current_effect == UNDERGLOW_EFFECT_LAYER_INDICATORS) { + if (!state.on || state.current_effect == UNDERGLOW_EFFECT_LAYER_INDICATORS) { return; } @@ -647,6 +646,11 @@ void zmk_rgb_set_ext_power(void) { } int zmk_rgb_underglow_on(void) { + zmk_rgb_underglow_transient_on(); + return zmk_rgb_underglow_save_state(); +} + +int zmk_rgb_underglow_transient_on(void) { if (!led_strip) return -ENODEV; @@ -656,7 +660,7 @@ int zmk_rgb_underglow_on(void) { state.animation_step = 0; k_timer_start(&underglow_tick, K_NO_WAIT, K_MSEC(25)); - return zmk_rgb_underglow_save_state(); + return 0; } static void zmk_rgb_underglow_off_handler(struct k_work *work) { @@ -669,6 +673,11 @@ static void zmk_rgb_underglow_off_handler(struct k_work *work) { K_WORK_DEFINE(underglow_off_work, zmk_rgb_underglow_off_handler); int zmk_rgb_underglow_off(void) { + zmk_rgb_underglow_transient_off(); + return zmk_rgb_underglow_save_state(); +} + +int zmk_rgb_underglow_transient_off(void) { if (!led_strip) return -ENODEV; @@ -678,7 +687,7 @@ int zmk_rgb_underglow_off(void) { state.on = false; zmk_rgb_set_ext_power(); - return zmk_rgb_underglow_save_state(); + return 0; } int zmk_rgb_underglow_calc_effect(int direction) { @@ -855,17 +864,19 @@ static int rgb_underglow_auto_state(bool target_wake_state) { sleep_state.is_awake = target_wake_state; if (sleep_state.is_awake) { - if (sleep_state.rgb_state_before_sleeping) { #ifdef UNDERGLOW_LAYER_ENABLED - zmk_rgb_underglow_set_layer(rgb_underglow_top_layer()); -#endif - return zmk_rgb_underglow_on(); + zmk_rgb_underglow_set_layer(rgb_underglow_top_layer()); + return 0; +#else + if (sleep_state.rgb_state_before_sleeping) { + return zmk_rgb_underglow_transient_on(); } else { - return zmk_rgb_underglow_off(); + return zmk_rgb_underglow_transient_off(); } +#endif } else { sleep_state.rgb_state_before_sleeping = state.on; - return zmk_rgb_underglow_off(); + return zmk_rgb_underglow_transient_off(); } } From 886cd1025dce2e52b2c0050b786ec33e37226335 Mon Sep 17 00:00:00 2001 From: darknao Date: Mon, 20 May 2024 19:51:09 +0200 Subject: [PATCH 09/25] fix: RGB_TOG also toggle underglow-layer --- app/src/rgb_underglow.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/app/src/rgb_underglow.c b/app/src/rgb_underglow.c index 6fa9cf83..8099c85c 100644 --- a/app/src/rgb_underglow.c +++ b/app/src/rgb_underglow.c @@ -82,6 +82,7 @@ struct rgb_underglow_state { uint16_t animation_step; bool on; bool status_active; + bool layer_enabled; uint16_t status_animation_step; }; @@ -475,7 +476,7 @@ static void zmk_rgb_underglow_apply_rgbmap(uint32_t rgbmap[], size_t rgbmap_len) } static void zmk_rgb_underglow_set_layer(uint8_t layer) { - if (state.current_effect != UNDERGLOW_EFFECT_LAYER_INDICATORS) + if (!state.layer_enabled) return; uint32_t *rgbmap = rgb_underglow_get_bindings(layer); @@ -607,7 +608,7 @@ int zmk_rgb_underglow_get_state(bool *on_off) { if (!led_strip) return -ENODEV; - *on_off = state.on; + *on_off = state.on || state.layer_enabled; return 0; } @@ -647,6 +648,9 @@ void zmk_rgb_set_ext_power(void) { int zmk_rgb_underglow_on(void) { zmk_rgb_underglow_transient_on(); + if (state.current_effect == UNDERGLOW_EFFECT_LAYER_INDICATORS) { + state.layer_enabled = true; + } return zmk_rgb_underglow_save_state(); } @@ -674,6 +678,7 @@ K_WORK_DEFINE(underglow_off_work, zmk_rgb_underglow_off_handler); int zmk_rgb_underglow_off(void) { zmk_rgb_underglow_transient_off(); + state.layer_enabled = false; return zmk_rgb_underglow_save_state(); } @@ -704,7 +709,7 @@ int zmk_rgb_underglow_select_effect(int effect) { state.current_effect = effect; state.animation_step = 0; - + state.layer_enabled = (effect == UNDERGLOW_EFFECT_LAYER_INDICATORS); return zmk_rgb_underglow_save_state(); } From bd5e07f842faf2b88bed6263793ff6f3ceca8a4d Mon Sep 17 00:00:00 2001 From: Nick Winans Date: Fri, 6 May 2022 00:19:08 -0500 Subject: [PATCH 10/25] feat(split): Increase split interval during idle --- app/src/split/bluetooth/CMakeLists.txt | 1 + app/src/split/bluetooth/Kconfig | 20 +++++ app/src/split/bluetooth/central.c | 1 - app/src/split/bluetooth/central_listener.c | 87 ++++++++++++++++++++++ 4 files changed, 108 insertions(+), 1 deletion(-) create mode 100644 app/src/split/bluetooth/central_listener.c diff --git a/app/src/split/bluetooth/CMakeLists.txt b/app/src/split/bluetooth/CMakeLists.txt index 2980eb59..4258b209 100644 --- a/app/src/split/bluetooth/CMakeLists.txt +++ b/app/src/split/bluetooth/CMakeLists.txt @@ -9,6 +9,7 @@ if (NOT CONFIG_ZMK_SPLIT_ROLE_CENTRAL) endif() if (CONFIG_ZMK_SPLIT_ROLE_CENTRAL) target_sources(app PRIVATE central.c) + target_sources_ifdef(CONFIG_ZMK_SPLIT_BLE_PREF_IDLE app PRIVATE central_listener.c) endif() if (CONFIG_ZMK_SPLIT_BLE_CENTRAL_BATTERY_LEVEL_PROXY) diff --git a/app/src/split/bluetooth/Kconfig b/app/src/split/bluetooth/Kconfig index 5f4a782f..c5f11945 100644 --- a/app/src/split/bluetooth/Kconfig +++ b/app/src/split/bluetooth/Kconfig @@ -74,6 +74,26 @@ config ZMK_SPLIT_BLE_PREF_TIMEOUT int "Supervision timeout to use for split central/peripheral connection" default 400 +config ZMK_SPLIT_BLE_PREF_IDLE + bool "Set slower split peripheral BLE params on idle to save power" + default y + +if ZMK_SPLIT_BLE_PREF_IDLE + +config ZMK_SPLIT_BLE_PREF_IDLE_INT + int "Peripheral idle connection interval in 1.25ms units" + default 18 + +config ZMK_SPLIT_BLE_PREF_IDLE_LATENCY + int "Peripheral idle latency in Connection Intervals" + default 10 + +config ZMK_SPLIT_BLE_PREF_IDLE_TIMEOUT + int "Peripheral idle supervision timeout in 10ms units" + default 400 + +endif # ZMK_SPLIT_BLE_PREF_IDLE + endif # ZMK_SPLIT_ROLE_CENTRAL if !ZMK_SPLIT_ROLE_CENTRAL diff --git a/app/src/split/bluetooth/central.c b/app/src/split/bluetooth/central.c index 6fdbcbd2..382c7bad 100644 --- a/app/src/split/bluetooth/central.c +++ b/app/src/split/bluetooth/central.c @@ -1178,7 +1178,6 @@ static void split_central_update_layers_callback(struct k_work *work) { LOG_DBG("Sent Layers over to peripheral"); raise_zmk_split_peripheral_layer_changed( (struct zmk_split_peripheral_layer_changed){.layers = layers}); - } } } diff --git a/app/src/split/bluetooth/central_listener.c b/app/src/split/bluetooth/central_listener.c new file mode 100644 index 00000000..87e471cb --- /dev/null +++ b/app/src/split/bluetooth/central_listener.c @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2022 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#include + +LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); + +#include +#include + +#include +#include + +static void set_sleep_params(struct bt_conn *conn, void *data) { + struct bt_conn_info info; + + bt_conn_get_info(conn, &info); + + if (info.role == BT_CONN_ROLE_CENTRAL) { + int err = + bt_conn_le_param_update(conn, BT_LE_CONN_PARAM(CONFIG_ZMK_SPLIT_BLE_PREF_IDLE_INT, + CONFIG_ZMK_SPLIT_BLE_PREF_IDLE_INT, + CONFIG_ZMK_SPLIT_BLE_PREF_IDLE_LATENCY, + CONFIG_ZMK_SPLIT_BLE_PREF_IDLE_TIMEOUT)); + + if (err) { + LOG_DBG("Failed to sleep split connection: %d", err); + } + } +} + +static void set_wake_params(struct bt_conn *conn, void *data) { + struct bt_conn_info info; + + bt_conn_get_info(conn, &info); + + if (info.role == BT_CONN_ROLE_CENTRAL) { + int err = bt_conn_le_param_update( + conn, + BT_LE_CONN_PARAM(CONFIG_ZMK_SPLIT_BLE_PREF_INT, CONFIG_ZMK_SPLIT_BLE_PREF_INT, + CONFIG_ZMK_SPLIT_BLE_PREF_LATENCY, CONFIG_ZMK_SPLIT_BLE_PREF_TIMEOUT)); + + if (err) { + LOG_DBG("Failed to wake up split connection: %d", err); + } + } +} + +static void sleep_all() { + LOG_DBG("Setting idle connection parameters on peripherals"); + + bt_conn_foreach(BT_CONN_TYPE_LE, set_sleep_params, NULL); +} + +static void wake_all() { + LOG_DBG("Waking up from idle connection parameters on peripherals"); + + bt_conn_foreach(BT_CONN_TYPE_LE, set_wake_params, NULL); +} + +int central_event_handler(const zmk_event_t *eh) { + struct zmk_activity_state_changed *ev = as_zmk_activity_state_changed(eh); + if (ev == NULL) { + return -ENOTSUP; + } + + switch (ev->state) { + case ZMK_ACTIVITY_ACTIVE: + wake_all(); + break; + case ZMK_ACTIVITY_IDLE: + sleep_all(); + break; + case ZMK_ACTIVITY_SLEEP: + break; + default: + LOG_WRN("Unhandled activity state: %d", ev->state); + return -EINVAL; + } + return 0; +} + +ZMK_LISTENER(central, central_event_handler); +ZMK_SUBSCRIPTION(central, zmk_activity_state_changed); From a1d67a4e92234c1b46493cfdd8992623d885506e Mon Sep 17 00:00:00 2001 From: darknao Date: Wed, 22 May 2024 13:19:28 +0200 Subject: [PATCH 11/25] fix: ZMK_RGB_UNDERGLOW_AUTO_OFF_IDLE not required for underglow-layer --- app/src/rgb_underglow.c | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/app/src/rgb_underglow.c b/app/src/rgb_underglow.c index 8099c85c..9ffd3fd0 100644 --- a/app/src/rgb_underglow.c +++ b/app/src/rgb_underglow.c @@ -53,7 +53,7 @@ LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); #endif #if DT_HAS_COMPAT_STATUS_OKAY(zmk_underglow_layer) -#define UNDERGLOW_LAYER_ENABLED +#define UNDERGLOW_LAYER_ENABLED 1 #endif #define STRIP_CHOSEN DT_CHOSEN(zmk_underglow) @@ -442,7 +442,7 @@ static inline struct led_rgb hue_sat(int hue, int sat) { return hsb_to_rgb(hsb_scale_min_max(hsb)); } -#ifdef UNDERGLOW_LAYER_ENABLED +#if IS_ENABLED(UNDERGLOW_LAYER_ENABLED) static struct led_rgb hex_to_rgb(uint8_t r, uint8_t g, uint8_t b) { struct zmk_led_hsb hsb = state.color; @@ -488,7 +488,7 @@ static void zmk_rgb_underglow_set_layer(uint8_t layer) { zmk_rgb_underglow_transient_off(); } } -#endif /* UNDERGLOW_LAYER_ENABLED */ +#endif /* IS_ENABLED(UNDERGLOW_LAYER_ENABLED) */ static void zmk_rgb_underglow_tick(struct k_work *work) { switch (state.current_effect) { @@ -850,7 +850,7 @@ int zmk_rgb_underglow_change_spd(int direction) { } #if IS_ENABLED(CONFIG_ZMK_RGB_UNDERGLOW_AUTO_OFF_IDLE) || \ - IS_ENABLED(CONFIG_ZMK_RGB_UNDERGLOW_AUTO_OFF_USB) + IS_ENABLED(CONFIG_ZMK_RGB_UNDERGLOW_AUTO_OFF_USB) || IS_ENABLED(UNDERGLOW_LAYER_ENABLED) struct rgb_underglow_sleep_state { bool is_awake; bool rgb_state_before_sleeping; @@ -869,7 +869,7 @@ static int rgb_underglow_auto_state(bool target_wake_state) { sleep_state.is_awake = target_wake_state; if (sleep_state.is_awake) { -#ifdef UNDERGLOW_LAYER_ENABLED +#if IS_ENABLED(UNDERGLOW_LAYER_ENABLED) zmk_rgb_underglow_set_layer(rgb_underglow_top_layer()); return 0; #else @@ -893,7 +893,7 @@ static int rgb_underglow_event_listener(const zmk_event_t *eh) { } #endif -#ifdef UNDERGLOW_LAYER_ENABLED +#if IS_ENABLED(UNDERGLOW_LAYER_ENABLED) if (as_zmk_split_peripheral_layer_changed(eh)) { const struct zmk_split_peripheral_layer_changed *ev = as_zmk_split_peripheral_layer_changed(eh); @@ -919,17 +919,19 @@ static int rgb_underglow_event_listener(const zmk_event_t *eh) { ZMK_LISTENER(rgb_underglow, rgb_underglow_event_listener); #endif // IS_ENABLED(CONFIG_ZMK_RGB_UNDERGLOW_AUTO_OFF_IDLE) || - // IS_ENABLED(CONFIG_ZMK_RGB_UNDERGLOW_AUTO_OFF_USB) + // IS_ENABLED(CONFIG_ZMK_RGB_UNDERGLOW_AUTO_OFF_USB) || + // IS_ENABLED(UNDERGLOW_LAYER_ENABLED) #if IS_ENABLED(CONFIG_ZMK_RGB_UNDERGLOW_AUTO_OFF_IDLE) ZMK_SUBSCRIPTION(rgb_underglow, zmk_activity_state_changed); -#ifdef UNDERGLOW_LAYER_ENABLED -ZMK_SUBSCRIPTION(rgb_underglow, zmk_split_peripheral_layer_changed); -#endif #endif #if IS_ENABLED(CONFIG_ZMK_RGB_UNDERGLOW_AUTO_OFF_USB) ZMK_SUBSCRIPTION(rgb_underglow, zmk_usb_conn_state_changed); #endif +#if IS_ENABLED(UNDERGLOW_LAYER_ENABLED) +ZMK_SUBSCRIPTION(rgb_underglow, zmk_split_peripheral_layer_changed); +#endif + SYS_INIT(zmk_rgb_underglow_init, APPLICATION, CONFIG_APPLICATION_INIT_PRIORITY); From f20c011fc6969148529631bcb75ec45269fcedf3 Mon Sep 17 00:00:00 2001 From: darknao Date: Thu, 23 May 2024 16:09:11 +0200 Subject: [PATCH 12/25] underglow-layer: enable with EXPERIMENTAL_RGB_LAYER Kconfig --- app/Kconfig | 4 ++++ app/src/rgb_underglow.c | 13 ++++++++----- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/app/Kconfig b/app/Kconfig index e3bfb37b..fde08909 100644 --- a/app/Kconfig +++ b/app/Kconfig @@ -335,6 +335,10 @@ config ZMK_RGB_UNDERGLOW_AUTO_OFF_USB bool "Turn off RGB underglow when USB is disconnected" depends on USB_DEVICE_STACK +config EXPERIMENTAL_RGB_LAYER + bool "Experimental per-key per-layer RGB underglow" + default n + endif # ZMK_RGB_UNDERGLOW menuconfig ZMK_BACKLIGHT diff --git a/app/src/rgb_underglow.c b/app/src/rgb_underglow.c index 9ffd3fd0..2aec0d07 100644 --- a/app/src/rgb_underglow.c +++ b/app/src/rgb_underglow.c @@ -52,7 +52,7 @@ LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); #endif -#if DT_HAS_COMPAT_STATUS_OKAY(zmk_underglow_layer) +#if DT_HAS_COMPAT_STATUS_OKAY(zmk_underglow_layer) && IS_ENABLED(CONFIG_EXPERIMENTAL_RGB_LAYER) #define UNDERGLOW_LAYER_ENABLED 1 #endif @@ -71,7 +71,9 @@ enum rgb_underglow_effect { UNDERGLOW_EFFECT_BREATHE, UNDERGLOW_EFFECT_SPECTRUM, UNDERGLOW_EFFECT_SWIRL, +#if IS_ENABLED(UNDERGLOW_LAYER_ENABLED) UNDERGLOW_EFFECT_LAYER_INDICATORS, +#endif UNDERGLOW_EFFECT_NUMBER // Used to track number of underglow effects }; @@ -504,9 +506,6 @@ 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: - // zmk_rgb_underglow_set_layer(); - break; } zmk_led_write_pixels(); @@ -515,7 +514,7 @@ static void zmk_rgb_underglow_tick(struct k_work *work) { K_WORK_DEFINE(underglow_tick_work, zmk_rgb_underglow_tick); static void zmk_rgb_underglow_tick_handler(struct k_timer *timer) { - if (!state.on || state.current_effect == UNDERGLOW_EFFECT_LAYER_INDICATORS) { + if (!state.on || state.layer_enabled) { return; } @@ -648,9 +647,11 @@ void zmk_rgb_set_ext_power(void) { int zmk_rgb_underglow_on(void) { zmk_rgb_underglow_transient_on(); +#if IS_ENABLED(UNDERGLOW_LAYER_ENABLED) if (state.current_effect == UNDERGLOW_EFFECT_LAYER_INDICATORS) { state.layer_enabled = true; } +#endif return zmk_rgb_underglow_save_state(); } @@ -709,7 +710,9 @@ int zmk_rgb_underglow_select_effect(int effect) { state.current_effect = effect; state.animation_step = 0; +#if IS_ENABLED(UNDERGLOW_LAYER_ENABLED) state.layer_enabled = (effect == UNDERGLOW_EFFECT_LAYER_INDICATORS); +#endif return zmk_rgb_underglow_save_state(); } From 8999793d2f52787706ace48c5f878f7eeb5ef1a3 Mon Sep 17 00:00:00 2001 From: darknao Date: Sat, 25 May 2024 13:30:49 +0200 Subject: [PATCH 13/25] underglow-layer: cut off ext power if all leds are off --- app/src/rgb_underglow.c | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/app/src/rgb_underglow.c b/app/src/rgb_underglow.c index 2aec0d07..cf8e45a7 100644 --- a/app/src/rgb_underglow.c +++ b/app/src/rgb_underglow.c @@ -455,7 +455,7 @@ static struct led_rgb hex_to_rgb(uint8_t r, uint8_t g, uint8_t b) { }; } -static void zmk_rgb_underglow_apply_rgbmap(uint32_t rgbmap[], size_t rgbmap_len) { +static int zmk_rgb_underglow_apply_rgbmap(uint32_t rgbmap[], size_t rgbmap_len) { // TODO: Glove80 specifics, move that part to board's devicetree #ifdef LEFT_HALF const uint8_t LED_MATRIX[] = {52, 53, 54, 69, 70, 71, 15, 27, 39, 51, 4, 14, 26, 38, @@ -466,6 +466,7 @@ static void zmk_rgb_underglow_apply_rgbmap(uint32_t rgbmap[], size_t rgbmap_len) 59, 75, 6, 18, 30, 42, 60, 76, 7, 19, 31, 43, 61, 77, 8, 20, 32, 44, 62, 78, 9, 21, 33, 45, 63, 79}; #endif + int rc = 0; for (int i = 0; i < STRIP_NUM_PIXELS; i++) { uint8_t midx = LED_MATRIX[i]; if (midx >= ZMK_KEYMAP_LEN) { @@ -473,8 +474,11 @@ static void zmk_rgb_underglow_apply_rgbmap(uint32_t rgbmap[], size_t rgbmap_len) } else { pixels[i] = hex_to_rgb((rgbmap[midx] & 0xFF0000) >> 16, (rgbmap[midx] & 0xFF00) >> 8, rgbmap[midx] & 0xFF); + if (rgbmap[midx] > 0) + rc = 1; } } + return rc; } static void zmk_rgb_underglow_set_layer(uint8_t layer) { @@ -482,12 +486,13 @@ static void zmk_rgb_underglow_set_layer(uint8_t layer) { return; uint32_t *rgbmap = rgb_underglow_get_bindings(layer); - if (rgbmap != NULL) { - zmk_rgb_underglow_apply_rgbmap(rgbmap, ZMK_KEYMAP_LEN); - zmk_rgb_underglow_transient_on(); + if (rgbmap != NULL && zmk_rgb_underglow_apply_rgbmap(rgbmap, ZMK_KEYMAP_LEN)) { + if (!state.on) + zmk_rgb_underglow_transient_on(); zmk_led_write_pixels(); } else { - zmk_rgb_underglow_transient_off(); + if (state.on) + zmk_rgb_underglow_transient_off(); } } #endif /* IS_ENABLED(UNDERGLOW_LAYER_ENABLED) */ From aa151f747305232e5e99aed3e9bfa44819edb223 Mon Sep 17 00:00:00 2001 From: ReFil <31960031+ReFil@users.noreply.github.com> Date: Thu, 2 Jan 2025 11:17:05 +0000 Subject: [PATCH 14/25] feat(split): Compile additional events on peripheral In preparation for the re-do of #2036 events that are useful for displays/indicators on the peripheral should be compiled on both sides so they can be raised on both sides --- app/CMakeLists.txt | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt index f01db605..da682567 100644 --- a/app/CMakeLists.txt +++ b/app/CMakeLists.txt @@ -71,16 +71,12 @@ if ((NOT CONFIG_ZMK_SPLIT) OR CONFIG_ZMK_SPLIT_ROLE_CENTRAL) target_sources(app PRIVATE src/behavior_queue.c) target_sources(app PRIVATE src/conditional_layer.c) target_sources(app PRIVATE src/endpoints.c) - target_sources(app PRIVATE src/events/endpoint_changed.c) target_sources(app PRIVATE src/hid_listener.c) target_sources(app PRIVATE src/keymap.c) - target_sources(app PRIVATE src/events/layer_state_changed.c) - target_sources(app PRIVATE src/events/modifiers_state_changed.c) target_sources(app PRIVATE src/events/keycode_state_changed.c) target_sources_ifdef(CONFIG_ZMK_HID_INDICATORS app PRIVATE src/hid_indicators.c) if (CONFIG_ZMK_BLE) - target_sources(app PRIVATE src/events/ble_active_profile_changed.c) target_sources(app PRIVATE src/behaviors/behavior_bt.c) target_sources(app PRIVATE src/ble.c) target_sources(app PRIVATE src/hog.c) @@ -95,8 +91,15 @@ 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_BLE app PRIVATE src/events/ble_active_profile_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) + +target_sources(app PRIVATE src/events/layer_state_changed.c) +target_sources(app PRIVATE src/events/modifiers_state_changed.c) +target_sources(app PRIVATE src/events/endpoint_changed.c) + add_subdirectory_ifdef(CONFIG_ZMK_SPLIT src/split) target_sources_ifdef(CONFIG_USB_DEVICE_STACK app PRIVATE src/usb.c) From f178f86a5401758304165e3f0b82dfff5a3e568a Mon Sep 17 00:00:00 2001 From: darknao Date: Sat, 4 Jan 2025 20:01:15 +0100 Subject: [PATCH 15/25] underglow-layer: use behaviors to control RGBs --- app/CMakeLists.txt | 3 + app/boards/arm/glove80/glove80_lh.dts | 8 ++ app/boards/arm/glove80/glove80_lh_defconfig | 26 ++++++ app/boards/arm/glove80/glove80_rh.dts | 8 ++ app/boards/arm/glove80/glove80_rh_defconfig | 4 + app/dts/behaviors.dtsi | 2 + app/dts/behaviors/ug_color.dtsi | 15 ++++ app/dts/behaviors/ug_indicators.dtsi | 33 ++++++++ .../zmk,behavior-underglow-color.yaml | 8 ++ .../zmk,behavior-underglow-indicators.yaml | 13 +++ app/dts/bindings/zmk,underglow-layer.yaml | 7 +- app/include/dt-bindings/zmk/hid_indicators.h | 9 +++ app/include/dt-bindings/zmk/rgb_colors.h | 3 +- .../zmk/events/underglow_color_changed.h | 15 ++++ app/include/zmk/rgb_underglow_layer.h | 13 ++- app/src/behaviors/behavior_underglow_color.c | 39 +++++++++ .../behaviors/behavior_underglow_indicators.c | 80 +++++++++++++++++++ app/src/events/underglow_color_changed.c | 10 +++ app/src/rgb_underglow.c | 63 ++++++++++----- app/src/rgb_underglow_layer.c | 19 ++++- 20 files changed, 352 insertions(+), 26 deletions(-) create mode 100644 app/dts/behaviors/ug_color.dtsi create mode 100644 app/dts/behaviors/ug_indicators.dtsi create mode 100644 app/dts/bindings/behaviors/zmk,behavior-underglow-color.yaml create mode 100644 app/dts/bindings/behaviors/zmk,behavior-underglow-indicators.yaml create mode 100644 app/include/dt-bindings/zmk/hid_indicators.h create mode 100644 app/include/zmk/events/underglow_color_changed.h create mode 100644 app/src/behaviors/behavior_underglow_color.c create mode 100644 app/src/behaviors/behavior_underglow_indicators.c create mode 100644 app/src/events/underglow_color_changed.c diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt index da682567..61fa1bba 100644 --- a/app/CMakeLists.txt +++ b/app/CMakeLists.txt @@ -84,6 +84,9 @@ if ((NOT CONFIG_ZMK_SPLIT) OR CONFIG_ZMK_SPLIT_ROLE_CENTRAL) endif() target_sources_ifdef(CONFIG_ZMK_RGB_UNDERGLOW app PRIVATE src/behaviors/behavior_rgb_underglow.c) +target_sources_ifdef(CONFIG_ZMK_RGB_UNDERGLOW app PRIVATE src/behaviors/behavior_underglow_color.c) +target_sources_ifdef(CONFIG_ZMK_RGB_UNDERGLOW app PRIVATE src/behaviors/behavior_underglow_indicators.c) +target_sources_ifdef(CONFIG_ZMK_RGB_UNDERGLOW app PRIVATE src/events/underglow_color_changed.c) target_sources_ifdef(CONFIG_ZMK_BACKLIGHT app PRIVATE src/behaviors/behavior_backlight.c) target_sources_ifdef(CONFIG_ZMK_BATTERY_REPORTING app PRIVATE src/events/battery_state_changed.c) diff --git a/app/boards/arm/glove80/glove80_lh.dts b/app/boards/arm/glove80/glove80_lh.dts index 2ed56688..7bd57742 100644 --- a/app/boards/arm/glove80/glove80_lh.dts +++ b/app/boards/arm/glove80/glove80_lh.dts @@ -19,6 +19,14 @@ zmk,battery = &vbatt; }; + underglow-layer { + compatible = "zmk,underglow-layer"; + pixel-lookup = <52>, <53>, <54>, <69>, <70>, <71>, <15>, <27>, <39>, <51>, <4>, <14>, <26>, <38>, + <50>, <68>, <3>, <13>, <25>, <37>, <49>, <67>, <2>, <12>, <24>, <36>, <48>, <66>, + <1>, <11>, <23>, <35>, <47>, <65>, <0>, <10>, <22>, <34>, <46>, <64>; + }; + + back_led_backlight: pwmleds { compatible = "pwm-leds"; pwm_led_0 { diff --git a/app/boards/arm/glove80/glove80_lh_defconfig b/app/boards/arm/glove80/glove80_lh_defconfig index a93f27cd..d36173bb 100644 --- a/app/boards/arm/glove80/glove80_lh_defconfig +++ b/app/boards/arm/glove80/glove80_lh_defconfig @@ -16,16 +16,32 @@ CONFIG_USB_DEVICE_VID=0x16c0 CONFIG_USB_DEVICE_MANUFACTURER="MoErgo" CONFIG_USB_DEVICE_SN="moergo.com:GLV80-0123456789ABCDEF" +CONFIG_BT_DEVICE_NAME="Glove80" + CONFIG_BT_DIS_PNP_PID=0x27db CONFIG_BT_DIS_PNP_VID=0x16c0 CONFIG_BT_DIS_MANUF="MoErgo" CONFIG_BT_DIS_MODEL="Glove80" +### Bluetooth configuration workarounds + +# Use higher radio transmit power CONFIG_BT_CTLR_TX_PWR_PLUS_8=y # Work-around for Windows bug with battery notifications CONFIG_BT_GATT_ENFORCE_SUBSCRIPTION=n +# Allow unauthenticated re-pairing for already paired hosts. This would permit +# an attacker that can spoof the host's peer address to "steal" the keyboard +# pairing by overwriting it, but without access to the previous keys it can't +# establish a MITM, and the sudden loss of the keyboard would be very obvious to +# the previously-connected host. +CONFIG_BT_SMP_ALLOW_UNAUTH_OVERWRITE=y +CONFIG_ZMK_BLE_PASSKEY_ENTRY=n + +# Fetch peripheral battery level for status display reporting +CONFIG_ZMK_SPLIT_BLE_CENTRAL_BATTERY_LEVEL_FETCHING=y + # Enable MPU CONFIG_ARM_MPU=y @@ -51,6 +67,9 @@ CONFIG_CLOCK_CONTROL_NRF_K32SRC_XTAL=y # Enable RGB underglow CONFIG_ZMK_RGB_UNDERGLOW=y +# disable EXT_POWER until underglow gets turned on +CONFIG_ZMK_EXT_POWER_START=n + CONFIG_ZMK_RGB_UNDERGLOW_EXT_POWER=y CONFIG_ZMK_RGB_UNDERGLOW_ON_START=n CONFIG_ZMK_RGB_UNDERGLOW_BRT_STEP=4 @@ -79,6 +98,13 @@ CONFIG_ZMK_BACKLIGHT_AUTO_OFF_USB=y # space. CONFIG_ZMK_HID_CONSUMER_REPORT_USAGES_BASIC=y +# Enable USB boot protocol support +CONFIG_ZMK_USB_BOOT=y +CONFIG_ZMK_HID_INDICATORS=y + +# Send HID indicator to peripherals +CONFIG_ZMK_SPLIT_PERIPHERAL_HID_INDICATORS=y + # Turn on debugging to disable optimization. Debug messages can result in larger # stacks, so enable stack protection and particularly a larger BLE peripheral stack. # CONFIG_DEBUG=y diff --git a/app/boards/arm/glove80/glove80_rh.dts b/app/boards/arm/glove80/glove80_rh.dts index 7b54f62c..0cb16518 100644 --- a/app/boards/arm/glove80/glove80_rh.dts +++ b/app/boards/arm/glove80/glove80_rh.dts @@ -20,6 +20,14 @@ zmk,battery = &vbatt; }; + underglow-layer { + compatible = "zmk,underglow-layer"; + pixel-lookup = <57>, <56>, <55>, <74>, <73>, <72>, <16>, <28>, <40>, <58>, <5>, <17>, <29>, <41>, + <59>, <75>, <6>, <18>, <30>, <42>, <60>, <76>, <7>, <19>, <31>, <43>, <61>, <77>, + <8>, <20>, <32>, <44>, <62>, <78>, <9>, <21>, <33>, <45>, <63>, <79>; + }; + + back_led_backlight: pwmleds { compatible = "pwm-leds"; pwm_led_0 { diff --git a/app/boards/arm/glove80/glove80_rh_defconfig b/app/boards/arm/glove80/glove80_rh_defconfig index ef29d682..b6795f5d 100644 --- a/app/boards/arm/glove80/glove80_rh_defconfig +++ b/app/boards/arm/glove80/glove80_rh_defconfig @@ -63,6 +63,10 @@ CONFIG_ZMK_RGB_UNDERGLOW_HUE_START=285 CONFIG_ZMK_RGB_UNDERGLOW_SAT_START=75 CONFIG_ZMK_RGB_UNDERGLOW_BRT_START=16 +# Enable HID indicators on peripheral +CONFIG_ZMK_HID_INDICATORS=y +CONFIG_ZMK_SPLIT_PERIPHERAL_HID_INDICATORS=y + # The power LED is implemented as a backlight # For now, the power LED is acting as a "USB connected" indicator CONFIG_ZMK_BACKLIGHT=y diff --git a/app/dts/behaviors.dtsi b/app/dts/behaviors.dtsi index 653b085d..ff56705d 100644 --- a/app/dts/behaviors.dtsi +++ b/app/dts/behaviors.dtsi @@ -28,3 +28,5 @@ #include #include #include +#include +#include diff --git a/app/dts/behaviors/ug_color.dtsi b/app/dts/behaviors/ug_color.dtsi new file mode 100644 index 00000000..15d6c863 --- /dev/null +++ b/app/dts/behaviors/ug_color.dtsi @@ -0,0 +1,15 @@ +/* + * Copyright (c) 2024 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +/ { + behaviors { + ug: ugcolor { + compatible = "zmk,behavior-underglow-color"; + #binding-cells = <1>; + display-name = "Underglow Color"; + }; + }; +}; diff --git a/app/dts/behaviors/ug_indicators.dtsi b/app/dts/behaviors/ug_indicators.dtsi new file mode 100644 index 00000000..b62c79b7 --- /dev/null +++ b/app/dts/behaviors/ug_indicators.dtsi @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2024 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#include + +/ { + behaviors { + ug_nl: ugnumlk { + compatible = "zmk,behavior-underglow-indicators"; + indicator = ; + #binding-cells = <2>; + display-name = "Underglow NumLock indicator"; + }; + + ug_cl: ugcapslk { + compatible = "zmk,behavior-underglow-indicators"; + indicator = ; + #binding-cells = <2>; + display-name = "Underglow CapsLock indicator"; + }; + + ug_sl: ugscrllk { + compatible = "zmk,behavior-underglow-indicators"; + indicator = ; + #binding-cells = <2>; + display-name = "Underglow ScrollLock indicator"; + }; + + }; +}; diff --git a/app/dts/bindings/behaviors/zmk,behavior-underglow-color.yaml b/app/dts/bindings/behaviors/zmk,behavior-underglow-color.yaml new file mode 100644 index 00000000..b3e90278 --- /dev/null +++ b/app/dts/bindings/behaviors/zmk,behavior-underglow-color.yaml @@ -0,0 +1,8 @@ +# Copyright (c) 2024, The ZMK Contributors +# SPDX-License-Identifier: MIT + +description: Set underglow to specified color + +compatible: "zmk,behavior-underglow-color" + +include: one_param.yaml diff --git a/app/dts/bindings/behaviors/zmk,behavior-underglow-indicators.yaml b/app/dts/bindings/behaviors/zmk,behavior-underglow-indicators.yaml new file mode 100644 index 00000000..553bb6c8 --- /dev/null +++ b/app/dts/bindings/behaviors/zmk,behavior-underglow-indicators.yaml @@ -0,0 +1,13 @@ +# Copyright (c) 2024, The ZMK Contributors +# SPDX-License-Identifier: MIT + +description: Set underglow for num lock indicator + +compatible: "zmk,behavior-underglow-indicators" + +include: two_param.yaml + +properties: + indicator: + type: int + default: 0 diff --git a/app/dts/bindings/zmk,underglow-layer.yaml b/app/dts/bindings/zmk,underglow-layer.yaml index 0f59c89a..62fec31b 100644 --- a/app/dts/bindings/zmk,underglow-layer.yaml +++ b/app/dts/bindings/zmk,underglow-layer.yaml @@ -3,12 +3,17 @@ description: | compatible: "zmk,underglow-layer" +properties: + pixel-lookup: + type: array + required: true + child-binding: description: "A layer to be used in a rgbmap" properties: bindings: - type: array + type: phandle-array required: true layer-id: type: int diff --git a/app/include/dt-bindings/zmk/hid_indicators.h b/app/include/dt-bindings/zmk/hid_indicators.h new file mode 100644 index 00000000..860c81db --- /dev/null +++ b/app/include/dt-bindings/zmk/hid_indicators.h @@ -0,0 +1,9 @@ +/* + * Copyright (c) 2020 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#define NUM_LOCK 0 +#define CAPS_LOCK 1 +#define SCROLL_LOCK 2 diff --git a/app/include/dt-bindings/zmk/rgb_colors.h b/app/include/dt-bindings/zmk/rgb_colors.h index 885c82d2..73e9cc1f 100644 --- a/app/include/dt-bindings/zmk/rgb_colors.h +++ b/app/include/dt-bindings/zmk/rgb_colors.h @@ -14,4 +14,5 @@ #define PURPLE 0x800080 #define PINK 0xffc0cb #define WHITE 0xffffff -#define ______ 0x000000 \ No newline at end of file +#define ___ 0x000000 +#define BLACK 0x000000 \ No newline at end of file diff --git a/app/include/zmk/events/underglow_color_changed.h b/app/include/zmk/events/underglow_color_changed.h new file mode 100644 index 00000000..24588ff2 --- /dev/null +++ b/app/include/zmk/events/underglow_color_changed.h @@ -0,0 +1,15 @@ +/* + * Copyright (c) 2024 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#pragma once + +#include + +struct zmk_underglow_color_changed { + uint32_t layers; +}; + +ZMK_EVENT_DECLARE(zmk_underglow_color_changed); diff --git a/app/include/zmk/rgb_underglow_layer.h b/app/include/zmk/rgb_underglow_layer.h index 3681a97e..879aff1a 100644 --- a/app/include/zmk/rgb_underglow_layer.h +++ b/app/include/zmk/rgb_underglow_layer.h @@ -12,7 +12,18 @@ #define ZMK_RGBMAP_LAYERS_LEN \ (DT_FOREACH_CHILD(DT_INST(0, zmk_underglow_layer), ZMK_RGB_CHILD_LEN_PLUS_ONE) 0) +#define ZMK_RGBMAP_EXTRACT_BINDING(idx, drv_inst) \ + { \ + .behavior_dev = DEVICE_DT_NAME(DT_PHANDLE_BY_IDX(drv_inst, bindings, idx)), \ + .param1 = COND_CODE_0(DT_PHA_HAS_CELL_AT_IDX(drv_inst, bindings, idx, param1), (0), \ + (DT_PHA_BY_IDX(drv_inst, bindings, idx, param1))), \ + .param2 = COND_CODE_0(DT_PHA_HAS_CELL_AT_IDX(drv_inst, bindings, idx, param2), (0), \ + (DT_PHA_BY_IDX(drv_inst, bindings, idx, param2))), \ + } + +const int rgb_pixel_lookup(int idx); const int zmk_rgbmap_id(uint8_t layer); -uint32_t *rgb_underglow_get_bindings(uint8_t layer); +const struct zmk_behavior_binding *rgb_underglow_get_bindings(uint8_t layer); + uint8_t rgb_underglow_top_layer_with_state(uint32_t state_to_test); uint8_t rgb_underglow_top_layer(void); \ No newline at end of file diff --git a/app/src/behaviors/behavior_underglow_color.c b/app/src/behaviors/behavior_underglow_color.c new file mode 100644 index 00000000..29000aab --- /dev/null +++ b/app/src/behaviors/behavior_underglow_color.c @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2024 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#define DT_DRV_COMPAT zmk_behavior_underglow_color + +// Dependencies +#include +#include +#include + +LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); + +#if DT_HAS_COMPAT_STATUS_OKAY(DT_DRV_COMPAT) + +// Initialization Function +static int underglow_color_init(const struct device *dev) { return 0; }; + +static int underglow_color_process(struct zmk_behavior_binding *binding, + struct zmk_behavior_binding_event event) { + return binding->param1; +} + +// API Structure +static const struct behavior_driver_api underglow_color_driver_api = { + .binding_pressed = underglow_color_process, + .locality = BEHAVIOR_LOCALITY_GLOBAL, +#if IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA) + .get_parameter_metadata = zmk_behavior_get_empty_param_metadata, +#endif // IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA) + +}; + +BEHAVIOR_DT_INST_DEFINE(0, underglow_color_init, NULL, NULL, NULL, POST_KERNEL, + CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, &underglow_color_driver_api); + +#endif /* DT_HAS_COMPAT_STATUS_OKAY(DT_DRV_COMPAT) */ diff --git a/app/src/behaviors/behavior_underglow_indicators.c b/app/src/behaviors/behavior_underglow_indicators.c new file mode 100644 index 00000000..d29a60ae --- /dev/null +++ b/app/src/behaviors/behavior_underglow_indicators.c @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2024 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#define DT_DRV_COMPAT zmk_behavior_underglow_indicators + +// Dependencies +#include +#include +#include +#include +#include +#include +#include + +LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); + +#if DT_HAS_COMPAT_STATUS_OKAY(DT_DRV_COMPAT) + +struct underglow_indicators_data { + zmk_hid_indicators_t indicators; + uint32_t layers; +}; + +struct underglow_indicators_config { + int indicator; +}; + +static int underglow_indicators_init(const struct device *dev) { return 0; }; + +static int underglow_indicators_process(struct zmk_behavior_binding *binding, + struct zmk_behavior_binding_event event) { + const struct device *dev = zmk_behavior_get_binding(binding->behavior_dev); + struct underglow_indicators_data *data = dev->data; + const struct underglow_indicators_config *config = dev->config; + data->layers |= BIT(event.layer); + + if (data->indicators & BIT(config->indicator)) + return binding->param2; + else + return binding->param1; +} + +static const struct behavior_driver_api underglow_indicators_driver_api = { + .binding_pressed = underglow_indicators_process, + .locality = BEHAVIOR_LOCALITY_GLOBAL, +#if IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA) + .get_parameter_metadata = zmk_behavior_get_empty_param_metadata, +#endif // IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA) +}; + +static int underglow_indicators_listener(const zmk_event_t *eh); + +ZMK_LISTENER(behavior_underglow_indicators, underglow_indicators_listener); +ZMK_SUBSCRIPTION(behavior_underglow_indicators, zmk_hid_indicators_changed); + +static struct underglow_indicators_data underglow_indicators_data = {.indicators = 0, .layers = 0}; + +static int underglow_indicators_listener(const zmk_event_t *eh) { + const struct zmk_hid_indicators_changed *ev = as_zmk_hid_indicators_changed(eh); + underglow_indicators_data.indicators = ev->indicators; + raise_zmk_underglow_color_changed( + (struct zmk_underglow_color_changed){.layers = underglow_indicators_data.layers}); + + return ZMK_EV_EVENT_BUBBLE; +} + +#define KP_INST(n) \ + static struct underglow_indicators_config underglow_indicators_config_##n = { \ + .indicator = DT_INST_PROP(n, indicator)}; \ + BEHAVIOR_DT_INST_DEFINE(n, underglow_indicators_init, NULL, &underglow_indicators_data, \ + &underglow_indicators_config_##n, POST_KERNEL, \ + CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, \ + &underglow_indicators_driver_api); + +DT_INST_FOREACH_STATUS_OKAY(KP_INST) + +#endif /* DT_HAS_COMPAT_STATUS_OKAY(DT_DRV_COMPAT) */ diff --git a/app/src/events/underglow_color_changed.c b/app/src/events/underglow_color_changed.c new file mode 100644 index 00000000..c00bdc6b --- /dev/null +++ b/app/src/events/underglow_color_changed.c @@ -0,0 +1,10 @@ +/* + * Copyright (c) 2020 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#include +#include + +ZMK_EVENT_IMPL(zmk_underglow_color_changed); \ No newline at end of file diff --git a/app/src/rgb_underglow.c b/app/src/rgb_underglow.c index cf8e45a7..ae065bdd 100644 --- a/app/src/rgb_underglow.c +++ b/app/src/rgb_underglow.c @@ -17,6 +17,7 @@ #include #include #include +#include #include #include @@ -25,6 +26,7 @@ #include #include +#include #include #include @@ -33,6 +35,8 @@ #include #include #include +#include + #include #include @@ -52,13 +56,13 @@ LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); #endif +#define STRIP_CHOSEN DT_CHOSEN(zmk_underglow) +#define STRIP_NUM_PIXELS DT_PROP(STRIP_CHOSEN, chain_length) + #if DT_HAS_COMPAT_STATUS_OKAY(zmk_underglow_layer) && IS_ENABLED(CONFIG_EXPERIMENTAL_RGB_LAYER) #define UNDERGLOW_LAYER_ENABLED 1 #endif -#define STRIP_CHOSEN DT_CHOSEN(zmk_underglow) -#define STRIP_NUM_PIXELS DT_PROP(STRIP_CHOSEN, chain_length) - #define HUE_MAX 360 #define SAT_MAX 100 #define BRT_MAX 100 @@ -455,27 +459,38 @@ static struct led_rgb hex_to_rgb(uint8_t r, uint8_t g, uint8_t b) { }; } -static int zmk_rgb_underglow_apply_rgbmap(uint32_t rgbmap[], size_t rgbmap_len) { -// TODO: Glove80 specifics, move that part to board's devicetree -#ifdef LEFT_HALF - const uint8_t LED_MATRIX[] = {52, 53, 54, 69, 70, 71, 15, 27, 39, 51, 4, 14, 26, 38, - 50, 68, 3, 13, 25, 37, 49, 67, 2, 12, 24, 36, 48, 66, - 1, 11, 23, 35, 47, 65, 0, 10, 22, 34, 46, 64}; -#else - const uint8_t LED_MATRIX[] = {57, 56, 55, 74, 73, 72, 16, 28, 40, 58, 5, 17, 29, 41, - 59, 75, 6, 18, 30, 42, 60, 76, 7, 19, 31, 43, 61, 77, - 8, 20, 32, 44, 62, 78, 9, 21, 33, 45, 63, 79}; -#endif +static int zmk_rgb_underglow_apply_rgbmap(struct zmk_behavior_binding *bindings, + size_t rgbmap_len) { int rc = 0; for (int i = 0; i < STRIP_NUM_PIXELS; i++) { - uint8_t midx = LED_MATRIX[i]; + uint8_t midx = rgb_pixel_lookup(i); if (midx >= ZMK_KEYMAP_LEN) { LOG_DBG("out of range"); } else { - pixels[i] = hex_to_rgb((rgbmap[midx] & 0xFF0000) >> 16, (rgbmap[midx] & 0xFF00) >> 8, - rgbmap[midx] & 0xFF); - if (rgbmap[midx] > 0) + const struct device *dev = zmk_behavior_get_binding(bindings[midx].behavior_dev); + + if (dev == NULL) { + continue; + } + + const struct behavior_driver_api *api = (const struct behavior_driver_api *)dev->api; + + if (api->binding_pressed == NULL) { + continue; + } + struct zmk_behavior_binding_event event = {.position = midx, + .timestamp = k_uptime_get()}; + + int color = + api->binding_pressed((const struct zmk_behavior_binding *)&bindings[midx], event); + + if (color > 0) { + pixels[i] = + hex_to_rgb((color & 0xFF0000) >> 16, (color & 0xFF00) >> 8, color & 0xFF); rc = 1; + } else { + pixels[i] = (struct led_rgb){r : 0, g : 0, b : 0}; + } } } return rc; @@ -485,7 +500,7 @@ static void zmk_rgb_underglow_set_layer(uint8_t layer) { if (!state.layer_enabled) return; - uint32_t *rgbmap = rgb_underglow_get_bindings(layer); + const struct zmk_behavior_binding *rgbmap = rgb_underglow_get_bindings(layer); if (rgbmap != NULL && zmk_rgb_underglow_apply_rgbmap(rgbmap, ZMK_KEYMAP_LEN)) { if (!state.on) zmk_rgb_underglow_transient_on(); @@ -914,6 +929,15 @@ static int rgb_underglow_event_listener(const zmk_event_t *eh) { zmk_rgb_underglow_set_layer(layer); return 0; } + if (as_zmk_underglow_color_changed(eh)) { + const struct zmk_underglow_color_changed *ev = as_zmk_underglow_color_changed(eh); + LOG_DBG("refresh layer %d", ev->layers); + uint8_t layer = rgb_underglow_top_layer(); + if ((ev->layers & (BIT(layer))) == BIT(layer)) { + zmk_rgb_underglow_set_layer(rgb_underglow_top_layer()); + } + return 0; + } #endif /* UNDERGLOW_LAYER_ENABLED */ #if IS_ENABLED(CONFIG_ZMK_RGB_UNDERGLOW_AUTO_OFF_USB) @@ -940,6 +964,7 @@ ZMK_SUBSCRIPTION(rgb_underglow, zmk_usb_conn_state_changed); #if IS_ENABLED(UNDERGLOW_LAYER_ENABLED) ZMK_SUBSCRIPTION(rgb_underglow, zmk_split_peripheral_layer_changed); +ZMK_SUBSCRIPTION(rgb_underglow, zmk_underglow_color_changed); #endif SYS_INIT(zmk_rgb_underglow_init, APPLICATION, CONFIG_APPLICATION_INIT_PRIORITY); diff --git a/app/src/rgb_underglow_layer.c b/app/src/rgb_underglow_layer.c index fb9876cf..6cd0ceb9 100644 --- a/app/src/rgb_underglow_layer.c +++ b/app/src/rgb_underglow_layer.c @@ -24,13 +24,24 @@ LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); #define UNDERGLOW_LAYER_ENABLED #define LAYER_ID(node) DT_PROP(node, layer_id) -#define RGB_BINDINGS(node) DT_PROP(node, bindings) -static uint32_t zmk_rgbmap[ZMK_RGBMAP_LAYERS_LEN][ZMK_KEYMAP_LEN] = { - DT_INST_FOREACH_CHILD_SEP(0, RGB_BINDINGS, (, ))}; +#define TRANSFORMED_RGB_LAYER(node) \ + {COND_CODE_1(DT_NODE_HAS_PROP(node, bindings), \ + (LISTIFY(DT_PROP_LEN(node, bindings), ZMK_RGBMAP_EXTRACT_BINDING, (, ), node)), \ + ())} + +#define RGBMAP_VAR(_name, _opts) \ + static _opts struct zmk_behavior_binding _name[ZMK_RGBMAP_LAYERS_LEN][ZMK_KEYMAP_LEN] = { \ + DT_INST_FOREACH_CHILD_STATUS_OKAY_SEP(0, TRANSFORMED_RGB_LAYER, (, ))}; + +RGBMAP_VAR(zmk_rgbmap, COND_CODE_1(IS_ENABLED(CONFIG_ZMK_KEYMAP_SETTINGS_STORAGE), (), (const))) + +const int pixel_lookup_table[] = DT_INST_PROP(0, pixel_lookup); static int zmk_rgbmap_ids[ZMK_RGBMAP_LAYERS_LEN] = {DT_INST_FOREACH_CHILD_SEP(0, LAYER_ID, (, ))}; +const int rgb_pixel_lookup(int idx) { return pixel_lookup_table[idx]; }; + const int zmk_rgbmap_id(uint8_t layer) { for (uint8_t i = 0; i < ZMK_RGBMAP_LAYERS_LEN; i++) { if (zmk_rgbmap_ids[i] == layer) { @@ -40,7 +51,7 @@ const int zmk_rgbmap_id(uint8_t layer) { return -1; } -uint32_t *rgb_underglow_get_bindings(uint8_t layer) { +const struct zmk_behavior_binding *rgb_underglow_get_bindings(uint8_t layer) { int rgblayer = zmk_rgbmap_id(layer); if (rgblayer == -1) { return NULL; From 0f43eeb0334e8fdfbfbacd9e2a6ee4dfb60495a7 Mon Sep 17 00:00:00 2001 From: darknao Date: Sat, 25 Jan 2025 15:36:32 +0100 Subject: [PATCH 16/25] fix: underglow-layer not showing on both side when powered on --- app/src/rgb_underglow.c | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/app/src/rgb_underglow.c b/app/src/rgb_underglow.c index ae065bdd..4b21c71c 100644 --- a/app/src/rgb_underglow.c +++ b/app/src/rgb_underglow.c @@ -558,7 +558,11 @@ static int rgb_settings_set(const char *name, size_t len, settings_read_cb read_ if (state.on) { k_timer_start(&underglow_tick, K_NO_WAIT, K_MSEC(50)); } - +#if IS_ENABLED(UNDERGLOW_LAYER_ENABLED) + if (state.layer_enabled) { + zmk_rgb_underglow_set_layer(rgb_underglow_top_layer()); + } +#endif return 0; } @@ -610,7 +614,11 @@ static int zmk_rgb_underglow_init(void) { if (state.on) { k_timer_start(&underglow_tick, K_NO_WAIT, K_MSEC(25)); } - +#if IS_ENABLED(UNDERGLOW_LAYER_ENABLED) + if (state.layer_enabled) { + zmk_rgb_underglow_set_layer(rgb_underglow_top_layer()); + } +#endif return 0; } @@ -893,15 +901,16 @@ static int rgb_underglow_auto_state(bool target_wake_state) { if (sleep_state.is_awake) { #if IS_ENABLED(UNDERGLOW_LAYER_ENABLED) - zmk_rgb_underglow_set_layer(rgb_underglow_top_layer()); - return 0; -#else + if (state.layer_enabled) { + zmk_rgb_underglow_set_layer(rgb_underglow_top_layer()); + return 0; + } +#endif if (sleep_state.rgb_state_before_sleeping) { return zmk_rgb_underglow_transient_on(); } else { return zmk_rgb_underglow_transient_off(); } -#endif } else { sleep_state.rgb_state_before_sleeping = state.on; return zmk_rgb_underglow_transient_off(); From 62dc6d8ec580882a7f3cfaa69386c1bf816454df Mon Sep 17 00:00:00 2001 From: darknao Date: Sat, 25 Jan 2025 15:37:33 +0100 Subject: [PATCH 17/25] underglow-layer: add battery indicators --- app/CMakeLists.txt | 1 + app/dts/behaviors.dtsi | 1 + app/dts/behaviors/ug_battery.dtsi | 34 ++++++++ .../zmk,behavior-underglow-battery.yaml | 12 +++ .../behaviors/behavior_underglow_battery.c | 77 +++++++++++++++++++ 5 files changed, 125 insertions(+) create mode 100644 app/dts/behaviors/ug_battery.dtsi create mode 100644 app/dts/bindings/behaviors/zmk,behavior-underglow-battery.yaml create mode 100644 app/src/behaviors/behavior_underglow_battery.c diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt index 61fa1bba..e062c2b6 100644 --- a/app/CMakeLists.txt +++ b/app/CMakeLists.txt @@ -86,6 +86,7 @@ endif() target_sources_ifdef(CONFIG_ZMK_RGB_UNDERGLOW app PRIVATE src/behaviors/behavior_rgb_underglow.c) target_sources_ifdef(CONFIG_ZMK_RGB_UNDERGLOW app PRIVATE src/behaviors/behavior_underglow_color.c) target_sources_ifdef(CONFIG_ZMK_RGB_UNDERGLOW app PRIVATE src/behaviors/behavior_underglow_indicators.c) +target_sources_ifdef(CONFIG_ZMK_RGB_UNDERGLOW app PRIVATE src/behaviors/behavior_underglow_battery.c) target_sources_ifdef(CONFIG_ZMK_RGB_UNDERGLOW app PRIVATE src/events/underglow_color_changed.c) target_sources_ifdef(CONFIG_ZMK_BACKLIGHT app PRIVATE src/behaviors/behavior_backlight.c) diff --git a/app/dts/behaviors.dtsi b/app/dts/behaviors.dtsi index ff56705d..1f87ef1a 100644 --- a/app/dts/behaviors.dtsi +++ b/app/dts/behaviors.dtsi @@ -30,3 +30,4 @@ #include #include #include +#include diff --git a/app/dts/behaviors/ug_battery.dtsi b/app/dts/behaviors/ug_battery.dtsi new file mode 100644 index 00000000..3e5f75cc --- /dev/null +++ b/app/dts/behaviors/ug_battery.dtsi @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2024 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +/ { + behaviors { + ug_b2: ugbat20 { + compatible = "zmk,behavior-underglow-battery"; + threshold = <20>; + #binding-cells = <2>; + display-name = "Underglow Battery level 20%"; + }; + ug_b4: ugbat40 { + compatible = "zmk,behavior-underglow-battery"; + threshold = <40>; + #binding-cells = <2>; + display-name = "Underglow Battery level 40%"; + }; + ug_b6: ugbat60 { + compatible = "zmk,behavior-underglow-battery"; + threshold = <60>; + #binding-cells = <2>; + display-name = "Underglow Battery level 60%"; + }; + ug_b8: ugbat80 { + compatible = "zmk,behavior-underglow-battery"; + threshold = <80>; + #binding-cells = <2>; + display-name = "Underglow Battery level 80%"; + }; + }; +}; diff --git a/app/dts/bindings/behaviors/zmk,behavior-underglow-battery.yaml b/app/dts/bindings/behaviors/zmk,behavior-underglow-battery.yaml new file mode 100644 index 00000000..4c535ea5 --- /dev/null +++ b/app/dts/bindings/behaviors/zmk,behavior-underglow-battery.yaml @@ -0,0 +1,12 @@ +# Copyright (c) 2024, The ZMK Contributors +# SPDX-License-Identifier: MIT + +description: Set underglow color based on battery level + +compatible: "zmk,behavior-underglow-battery" + +include: two_param.yaml + +properties: + threshold: + type: int diff --git a/app/src/behaviors/behavior_underglow_battery.c b/app/src/behaviors/behavior_underglow_battery.c new file mode 100644 index 00000000..821bf30a --- /dev/null +++ b/app/src/behaviors/behavior_underglow_battery.c @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2024 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#define DT_DRV_COMPAT zmk_behavior_underglow_battery + +// Dependencies +#include +#include +#include +#include +#include +#include +#include + +LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); + +#if DT_HAS_COMPAT_STATUS_OKAY(DT_DRV_COMPAT) + +struct underglow_battery_data { + uint32_t layers; +}; + +struct underglow_battery_config { + int threshold; +}; + +static struct underglow_battery_data underglow_battery_data = {.layers = 0}; + +static int underglow_battery_init(const struct device *dev) { return 0; }; + +static int underglow_battery_process(struct zmk_behavior_binding *binding, + struct zmk_behavior_binding_event event) { + const struct device *dev = zmk_behavior_get_binding(binding->behavior_dev); + const struct underglow_battery_config *config = dev->config; + struct underglow_battery_data *data = dev->data; + data->layers |= BIT(event.layer); + int bat = zmk_battery_state_of_charge(); + + if (bat >= config->threshold) + return binding->param2; + else + return binding->param1; +} + +static const struct behavior_driver_api underglow_battery_driver_api = { + .binding_pressed = underglow_battery_process, + .locality = BEHAVIOR_LOCALITY_GLOBAL, +#if IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA) + .get_parameter_metadata = zmk_behavior_get_empty_param_metadata, +#endif // IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA) +}; + +static int underglow_battery_listener(const zmk_event_t *eh); + +ZMK_LISTENER(behavior_underglow_battery, underglow_battery_listener); +ZMK_SUBSCRIPTION(behavior_underglow_battery, zmk_battery_state_changed); + +static int underglow_battery_listener(const zmk_event_t *eh) { + raise_zmk_underglow_color_changed( + (struct zmk_underglow_color_changed){.layers = underglow_battery_data.layers}); + + return ZMK_EV_EVENT_BUBBLE; +} + +#define KP_INST(n) \ + static struct underglow_battery_config underglow_battery_config_##n = { \ + .threshold = DT_INST_PROP(n, threshold)}; \ + BEHAVIOR_DT_INST_DEFINE(n, underglow_battery_init, NULL, &underglow_battery_data, \ + &underglow_battery_config_##n, POST_KERNEL, \ + CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, &underglow_battery_driver_api); + +DT_INST_FOREACH_STATUS_OKAY(KP_INST) + +#endif /* DT_HAS_COMPAT_STATUS_OKAY(DT_DRV_COMPAT) */ From 01f8f363f87c5b66f4d7f5d3a8f9de6606154ece Mon Sep 17 00:00:00 2001 From: darknao Date: Sat, 15 Mar 2025 14:40:27 +0100 Subject: [PATCH 18/25] underglow-layer: add fade-delay property --- app/dts/bindings/zmk,underglow-layer.yaml | 4 + app/include/zmk/rgb_underglow_layer.h | 2 + app/src/rgb_underglow.c | 162 +++++++++++++--------- app/src/rgb_underglow_layer.c | 4 + 4 files changed, 105 insertions(+), 67 deletions(-) diff --git a/app/dts/bindings/zmk,underglow-layer.yaml b/app/dts/bindings/zmk,underglow-layer.yaml index 62fec31b..28221039 100644 --- a/app/dts/bindings/zmk,underglow-layer.yaml +++ b/app/dts/bindings/zmk,underglow-layer.yaml @@ -18,3 +18,7 @@ child-binding: layer-id: type: int required: true + fade-delay: + type: int + required: false + default: -1 diff --git a/app/include/zmk/rgb_underglow_layer.h b/app/include/zmk/rgb_underglow_layer.h index 879aff1a..1b14359e 100644 --- a/app/include/zmk/rgb_underglow_layer.h +++ b/app/include/zmk/rgb_underglow_layer.h @@ -23,6 +23,8 @@ const int rgb_pixel_lookup(int idx); const int zmk_rgbmap_id(uint8_t layer); +const int zmk_rgbmap_fade_delay(uint8_t layer); + const struct zmk_behavior_binding *rgb_underglow_get_bindings(uint8_t layer); uint8_t rgb_underglow_top_layer_with_state(uint32_t state_to_test); diff --git a/app/src/rgb_underglow.c b/app/src/rgb_underglow.c index 4b21c71c..440ac399 100644 --- a/app/src/rgb_underglow.c +++ b/app/src/rgb_underglow.c @@ -61,6 +61,7 @@ LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); #if DT_HAS_COMPAT_STATUS_OKAY(zmk_underglow_layer) && IS_ENABLED(CONFIG_EXPERIMENTAL_RGB_LAYER) #define UNDERGLOW_LAYER_ENABLED 1 +static void zmk_rgb_underglow_set_layer(uint8_t layer); #endif #define HUE_MAX 360 @@ -210,6 +211,23 @@ static void zmk_rgb_underglow_effect_swirl(void) { state.animation_step = state.animation_step % HUE_MAX; } +static void zmk_rgb_underglow_effect_layer(void) { + bool active = false; + for (int i = 0; i < STRIP_NUM_PIXELS; i++) { + pixels[i].r -= state.animation_speed < pixels[i].r ? state.animation_speed : pixels[i].r; + pixels[i].g -= state.animation_speed < pixels[i].g ? state.animation_speed : pixels[i].g; + pixels[i].b -= state.animation_speed < pixels[i].b ? state.animation_speed : pixels[i].b; + if (pixels[i].r || pixels[i].g || pixels[i].b) { + active = true; + } + } + state.animation_step += state.animation_speed; + + if (state.animation_step > 255 || !active) { + zmk_rgb_underglow_transient_off(); + } +} + static int zmk_led_generate_status(void); static void zmk_led_write_pixels(void) { @@ -448,70 +466,6 @@ static inline struct led_rgb hue_sat(int hue, int sat) { return hsb_to_rgb(hsb_scale_min_max(hsb)); } -#if IS_ENABLED(UNDERGLOW_LAYER_ENABLED) - -static struct led_rgb hex_to_rgb(uint8_t r, uint8_t g, uint8_t b) { - struct zmk_led_hsb hsb = state.color; - return (struct led_rgb){ - r : (hsb.b * (r)) / 0xff, - g : (hsb.b * (g)) / 0xff, - b : (hsb.b * (b)) / 0xff - }; -} - -static int zmk_rgb_underglow_apply_rgbmap(struct zmk_behavior_binding *bindings, - size_t rgbmap_len) { - int rc = 0; - for (int i = 0; i < STRIP_NUM_PIXELS; i++) { - uint8_t midx = rgb_pixel_lookup(i); - if (midx >= ZMK_KEYMAP_LEN) { - LOG_DBG("out of range"); - } else { - const struct device *dev = zmk_behavior_get_binding(bindings[midx].behavior_dev); - - if (dev == NULL) { - continue; - } - - const struct behavior_driver_api *api = (const struct behavior_driver_api *)dev->api; - - if (api->binding_pressed == NULL) { - continue; - } - struct zmk_behavior_binding_event event = {.position = midx, - .timestamp = k_uptime_get()}; - - int color = - api->binding_pressed((const struct zmk_behavior_binding *)&bindings[midx], event); - - if (color > 0) { - pixels[i] = - hex_to_rgb((color & 0xFF0000) >> 16, (color & 0xFF00) >> 8, color & 0xFF); - rc = 1; - } else { - pixels[i] = (struct led_rgb){r : 0, g : 0, b : 0}; - } - } - } - return rc; -} - -static void zmk_rgb_underglow_set_layer(uint8_t layer) { - if (!state.layer_enabled) - return; - - const struct zmk_behavior_binding *rgbmap = rgb_underglow_get_bindings(layer); - if (rgbmap != NULL && zmk_rgb_underglow_apply_rgbmap(rgbmap, ZMK_KEYMAP_LEN)) { - if (!state.on) - zmk_rgb_underglow_transient_on(); - zmk_led_write_pixels(); - } else { - if (state.on) - zmk_rgb_underglow_transient_off(); - } -} -#endif /* IS_ENABLED(UNDERGLOW_LAYER_ENABLED) */ - static void zmk_rgb_underglow_tick(struct k_work *work) { switch (state.current_effect) { case UNDERGLOW_EFFECT_SOLID: @@ -526,6 +480,11 @@ static void zmk_rgb_underglow_tick(struct k_work *work) { case UNDERGLOW_EFFECT_SWIRL: zmk_rgb_underglow_effect_swirl(); break; +#if IS_ENABLED(UNDERGLOW_LAYER_ENABLED) + case UNDERGLOW_EFFECT_LAYER_INDICATORS: + zmk_rgb_underglow_effect_layer(); + break; +#endif } zmk_led_write_pixels(); @@ -534,7 +493,7 @@ static void zmk_rgb_underglow_tick(struct k_work *work) { K_WORK_DEFINE(underglow_tick_work, zmk_rgb_underglow_tick); static void zmk_rgb_underglow_tick_handler(struct k_timer *timer) { - if (!state.on || state.layer_enabled) { + if (!state.on) { return; } @@ -547,7 +506,6 @@ K_TIMER_DEFINE(underglow_tick, zmk_rgb_underglow_tick_handler, NULL); static int rgb_settings_set(const char *name, size_t len, settings_read_cb read_cb, void *cb_arg) { const char *next; int rc; - if (settings_name_steq(name, "state", &next) && !next) { if (len != sizeof(state)) { return -EINVAL; @@ -583,7 +541,6 @@ static struct k_work_delayable underglow_save_work; static int zmk_rgb_underglow_init(void) { led_strip = DEVICE_DT_GET(STRIP_CHOSEN); - #if IS_ENABLED(CONFIG_ZMK_RGB_UNDERGLOW_EXT_POWER) if (!device_is_ready(ext_power)) { LOG_ERR("External power device \"%s\" is not ready", ext_power->name); @@ -752,6 +709,77 @@ int zmk_rgb_underglow_toggle(void) { return state.on ? zmk_rgb_underglow_off() : zmk_rgb_underglow_on(); } +#if IS_ENABLED(UNDERGLOW_LAYER_ENABLED) + +static struct led_rgb hex_to_rgb(uint8_t r, uint8_t g, uint8_t b) { + struct zmk_led_hsb hsb = state.color; + return (struct led_rgb){ + r : (hsb.b * (r)) / 0xff, + g : (hsb.b * (g)) / 0xff, + b : (hsb.b * (b)) / 0xff + }; +} + +static int zmk_rgb_underglow_apply_rgbmap(const struct zmk_behavior_binding *bindings, + size_t rgbmap_len) { + int rc = 0; + for (int i = 0; i < STRIP_NUM_PIXELS; i++) { + uint8_t midx = rgb_pixel_lookup(i); + if (midx >= ZMK_KEYMAP_LEN) { + LOG_DBG("out of range"); + } else { + const struct device *dev = zmk_behavior_get_binding(bindings[midx].behavior_dev); + + if (dev == NULL) { + continue; + } + + const struct behavior_driver_api *api = (const struct behavior_driver_api *)dev->api; + + if (api->binding_pressed == NULL) { + continue; + } + struct zmk_behavior_binding_event event = {.position = midx, + .timestamp = k_uptime_get()}; + + int color = api->binding_pressed((struct zmk_behavior_binding *)&bindings[midx], event); + + if (color > 0) { + pixels[i] = + hex_to_rgb((color & 0xFF0000) >> 16, (color & 0xFF00) >> 8, color & 0xFF); + rc = 1; + } else { + pixels[i] = (struct led_rgb){r : 0, g : 0, b : 0}; + } + } + } + return rc; +} + +static void zmk_rgb_underglow_set_layer(uint8_t layer) { + LOG_DBG("state.layer: %d state.on: %d", state.layer_enabled, state.on); + if (!state.layer_enabled) + return; + + const struct zmk_behavior_binding *rgbmap = rgb_underglow_get_bindings(layer); + if (rgbmap != NULL && zmk_rgb_underglow_apply_rgbmap(rgbmap, ZMK_KEYMAP_LEN)) { + if (!state.on) + zmk_rgb_underglow_transient_on(); + k_timer_stop(&underglow_tick); + state.animation_step = 0; + int fade_delay = zmk_rgbmap_fade_delay(layer); + if (fade_delay >= 0) { + k_timer_start(&underglow_tick, K_SECONDS(fade_delay), K_MSEC(50)); + } + LOG_DBG("write pixels"); + zmk_led_write_pixels(); + } else { + if (state.on) + zmk_rgb_underglow_transient_off(); + } +} +#endif /* IS_ENABLED(UNDERGLOW_LAYER_ENABLED) */ + static void zmk_led_write_pixels_work(struct k_work *work); static void zmk_rgb_underglow_status_update(struct k_timer *timer); diff --git a/app/src/rgb_underglow_layer.c b/app/src/rgb_underglow_layer.c index 6cd0ceb9..d3fbbc6e 100644 --- a/app/src/rgb_underglow_layer.c +++ b/app/src/rgb_underglow_layer.c @@ -24,6 +24,7 @@ LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); #define UNDERGLOW_LAYER_ENABLED #define LAYER_ID(node) DT_PROP(node, layer_id) +#define FADE_DELAY(node) DT_PROP(node, fade_delay) #define TRANSFORMED_RGB_LAYER(node) \ {COND_CODE_1(DT_NODE_HAS_PROP(node, bindings), \ @@ -39,6 +40,7 @@ RGBMAP_VAR(zmk_rgbmap, COND_CODE_1(IS_ENABLED(CONFIG_ZMK_KEYMAP_SETTINGS_STORAGE const int pixel_lookup_table[] = DT_INST_PROP(0, pixel_lookup); static int zmk_rgbmap_ids[ZMK_RGBMAP_LAYERS_LEN] = {DT_INST_FOREACH_CHILD_SEP(0, LAYER_ID, (, ))}; +static int zmk_rgbmap_fds[ZMK_RGBMAP_LAYERS_LEN] = {DT_INST_FOREACH_CHILD_SEP(0, FADE_DELAY, (, ))}; const int rgb_pixel_lookup(int idx) { return pixel_lookup_table[idx]; }; @@ -51,6 +53,8 @@ const int zmk_rgbmap_id(uint8_t layer) { return -1; } +const int zmk_rgbmap_fade_delay(uint8_t layer) { return zmk_rgbmap_fds[zmk_rgbmap_id(layer)]; } + const struct zmk_behavior_binding *rgb_underglow_get_bindings(uint8_t layer) { int rgblayer = zmk_rgbmap_id(layer); if (rgblayer == -1) { From 28a1c4697e34a2eda7165296d270e13803589cb5 Mon Sep 17 00:00:00 2001 From: darknao Date: Sat, 2 Aug 2025 14:52:07 +0200 Subject: [PATCH 19/25] underglow-layer: keep RGB asleep on battery update --- .../zmk/events/underglow_color_changed.h | 1 + .../behaviors/behavior_underglow_battery.c | 4 +-- .../behaviors/behavior_underglow_indicators.c | 4 +-- app/src/rgb_underglow.c | 31 +++++++++++-------- 4 files changed, 23 insertions(+), 17 deletions(-) diff --git a/app/include/zmk/events/underglow_color_changed.h b/app/include/zmk/events/underglow_color_changed.h index 24588ff2..0d0ddf37 100644 --- a/app/include/zmk/events/underglow_color_changed.h +++ b/app/include/zmk/events/underglow_color_changed.h @@ -10,6 +10,7 @@ struct zmk_underglow_color_changed { uint32_t layers; + bool wakeup; }; ZMK_EVENT_DECLARE(zmk_underglow_color_changed); diff --git a/app/src/behaviors/behavior_underglow_battery.c b/app/src/behaviors/behavior_underglow_battery.c index 821bf30a..e369f842 100644 --- a/app/src/behaviors/behavior_underglow_battery.c +++ b/app/src/behaviors/behavior_underglow_battery.c @@ -59,8 +59,8 @@ ZMK_LISTENER(behavior_underglow_battery, underglow_battery_listener); ZMK_SUBSCRIPTION(behavior_underglow_battery, zmk_battery_state_changed); static int underglow_battery_listener(const zmk_event_t *eh) { - raise_zmk_underglow_color_changed( - (struct zmk_underglow_color_changed){.layers = underglow_battery_data.layers}); + raise_zmk_underglow_color_changed((struct zmk_underglow_color_changed){ + .layers = underglow_battery_data.layers, .wakeup = false}); return ZMK_EV_EVENT_BUBBLE; } diff --git a/app/src/behaviors/behavior_underglow_indicators.c b/app/src/behaviors/behavior_underglow_indicators.c index d29a60ae..92ca2519 100644 --- a/app/src/behaviors/behavior_underglow_indicators.c +++ b/app/src/behaviors/behavior_underglow_indicators.c @@ -61,8 +61,8 @@ static struct underglow_indicators_data underglow_indicators_data = {.indicators static int underglow_indicators_listener(const zmk_event_t *eh) { const struct zmk_hid_indicators_changed *ev = as_zmk_hid_indicators_changed(eh); underglow_indicators_data.indicators = ev->indicators; - raise_zmk_underglow_color_changed( - (struct zmk_underglow_color_changed){.layers = underglow_indicators_data.layers}); + raise_zmk_underglow_color_changed((struct zmk_underglow_color_changed){ + .layers = underglow_indicators_data.layers, .wakeup = true}); return ZMK_EV_EVENT_BUBBLE; } diff --git a/app/src/rgb_underglow.c b/app/src/rgb_underglow.c index 440ac399..9af51dc8 100644 --- a/app/src/rgb_underglow.c +++ b/app/src/rgb_underglow.c @@ -61,7 +61,7 @@ LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); #if DT_HAS_COMPAT_STATUS_OKAY(zmk_underglow_layer) && IS_ENABLED(CONFIG_EXPERIMENTAL_RGB_LAYER) #define UNDERGLOW_LAYER_ENABLED 1 -static void zmk_rgb_underglow_set_layer(uint8_t layer); +static void zmk_rgb_underglow_set_layer(uint8_t layer, bool wakeup); #endif #define HUE_MAX 360 @@ -518,7 +518,7 @@ static int rgb_settings_set(const char *name, size_t len, settings_read_cb read_ } #if IS_ENABLED(UNDERGLOW_LAYER_ENABLED) if (state.layer_enabled) { - zmk_rgb_underglow_set_layer(rgb_underglow_top_layer()); + zmk_rgb_underglow_set_layer(rgb_underglow_top_layer(), true); } #endif return 0; @@ -573,7 +573,7 @@ static int zmk_rgb_underglow_init(void) { } #if IS_ENABLED(UNDERGLOW_LAYER_ENABLED) if (state.layer_enabled) { - zmk_rgb_underglow_set_layer(rgb_underglow_top_layer()); + zmk_rgb_underglow_set_layer(rgb_underglow_top_layer(), true); } #endif return 0; @@ -721,7 +721,7 @@ static struct led_rgb hex_to_rgb(uint8_t r, uint8_t g, uint8_t b) { } static int zmk_rgb_underglow_apply_rgbmap(const struct zmk_behavior_binding *bindings, - size_t rgbmap_len) { + size_t rgbmap_len, uint8_t layer) { int rc = 0; for (int i = 0; i < STRIP_NUM_PIXELS; i++) { uint8_t midx = rgb_pixel_lookup(i); @@ -739,8 +739,8 @@ static int zmk_rgb_underglow_apply_rgbmap(const struct zmk_behavior_binding *bin if (api->binding_pressed == NULL) { continue; } - struct zmk_behavior_binding_event event = {.position = midx, - .timestamp = k_uptime_get()}; + struct zmk_behavior_binding_event event = { + .position = midx, .layer = layer, .timestamp = k_uptime_get()}; int color = api->binding_pressed((struct zmk_behavior_binding *)&bindings[midx], event); @@ -756,15 +756,20 @@ static int zmk_rgb_underglow_apply_rgbmap(const struct zmk_behavior_binding *bin return rc; } -static void zmk_rgb_underglow_set_layer(uint8_t layer) { +static void zmk_rgb_underglow_set_layer(uint8_t layer, bool wakeup) { LOG_DBG("state.layer: %d state.on: %d", state.layer_enabled, state.on); if (!state.layer_enabled) return; const struct zmk_behavior_binding *rgbmap = rgb_underglow_get_bindings(layer); - if (rgbmap != NULL && zmk_rgb_underglow_apply_rgbmap(rgbmap, ZMK_KEYMAP_LEN)) { - if (!state.on) + if (rgbmap != NULL && zmk_rgb_underglow_apply_rgbmap(rgbmap, ZMK_KEYMAP_LEN, layer)) { + if (!state.on) { + if (!wakeup) { + LOG_DBG("rgb off and no wakeup, abort refresh"); + return; + } zmk_rgb_underglow_transient_on(); + } k_timer_stop(&underglow_tick); state.animation_step = 0; int fade_delay = zmk_rgbmap_fade_delay(layer); @@ -930,7 +935,7 @@ static int rgb_underglow_auto_state(bool target_wake_state) { if (sleep_state.is_awake) { #if IS_ENABLED(UNDERGLOW_LAYER_ENABLED) if (state.layer_enabled) { - zmk_rgb_underglow_set_layer(rgb_underglow_top_layer()); + zmk_rgb_underglow_set_layer(rgb_underglow_top_layer(), true); return 0; } #endif @@ -963,15 +968,15 @@ static int rgb_underglow_event_listener(const zmk_event_t *eh) { #endif uint8_t layer = rgb_underglow_top_layer(); LOG_DBG("top layer: %d", layer); - zmk_rgb_underglow_set_layer(layer); + zmk_rgb_underglow_set_layer(layer, true); return 0; } if (as_zmk_underglow_color_changed(eh)) { const struct zmk_underglow_color_changed *ev = as_zmk_underglow_color_changed(eh); - LOG_DBG("refresh layer %d", ev->layers); uint8_t layer = rgb_underglow_top_layer(); + LOG_DBG("refresh layers %d, current: %d, wakeup: %d", ev->layers, layer, ev->wakeup); if ((ev->layers & (BIT(layer))) == BIT(layer)) { - zmk_rgb_underglow_set_layer(rgb_underglow_top_layer()); + zmk_rgb_underglow_set_layer(rgb_underglow_top_layer(), ev->wakeup); } return 0; } From a904f76092b500cf450afbf21c929c290bd046da Mon Sep 17 00:00:00 2001 From: afiqzudinhadi Date: Wed, 24 Jun 2026 20:02:28 +0800 Subject: [PATCH 20/25] fix: guard split_peripheral_layer_changed behind CONFIG_ZMK_SPLIT Without this guard, non-split builds (e.g. settings_reset) fail with undefined reference to zmk_event_zmk_split_peripheral_layer_changed. --- app/src/activity.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/src/activity.c b/app/src/activity.c index 161771df..570b1462 100644 --- a/app/src/activity.c +++ b/app/src/activity.c @@ -16,7 +16,9 @@ LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); #include #include #include +#if IS_ENABLED(CONFIG_ZMK_SPLIT) #include +#endif #include #include @@ -110,7 +112,9 @@ static int activity_init(void) { ZMK_LISTENER(activity, activity_event_listener); ZMK_SUBSCRIPTION(activity, zmk_position_state_changed); ZMK_SUBSCRIPTION(activity, zmk_sensor_event); +#if IS_ENABLED(CONFIG_ZMK_SPLIT) ZMK_SUBSCRIPTION(activity, zmk_split_peripheral_layer_changed); +#endif #if IS_ENABLED(CONFIG_ZMK_POINTING) From aa79502d5aca48ac9e95013e2a181f80723eaaea Mon Sep 17 00:00:00 2001 From: afiqzudinhadi Date: Wed, 24 Jun 2026 20:03:22 +0800 Subject: [PATCH 21/25] fix: guard ext_power usage behind CONFIG_ZMK_RGB_UNDERGLOW_EXT_POWER The battery low-power code referenced ext_power unconditionally, causing build failure when CONFIG_ZMK_RGB_UNDERGLOW_EXT_POWER=n. --- app/src/rgb_underglow.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/src/rgb_underglow.c b/app/src/rgb_underglow.c index 9af51dc8..24ffd875 100644 --- a/app/src/rgb_underglow.c +++ b/app/src/rgb_underglow.c @@ -254,6 +254,7 @@ static void zmk_led_write_pixels(void) { // battery below minimum charge if (bat0 < 10) { memset(pixels, 0, sizeof(struct led_rgb) * STRIP_NUM_PIXELS); +#if IS_ENABLED(CONFIG_ZMK_RGB_UNDERGLOW_EXT_POWER) if (state.on) { int c_power = ext_power_get(ext_power); if (c_power && !state.status_active) { @@ -262,6 +263,7 @@ static void zmk_led_write_pixels(void) { reset_ext_power = true; } } +#endif } if (blend == 0) { From 9ab9c4619f265f35893882147856c602fd34b301 Mon Sep 17 00:00:00 2001 From: afiqzudinhadi Date: Thu, 25 Jun 2026 10:44:13 +0800 Subject: [PATCH 22/25] fix: extend EFF_START range to include layer indicators (effect 4) darknao added UNDERGLOW_EFFECT_LAYER_INDICATORS as effect #4 but did not update the Kconfig range from [0,3] to [0,4]. --- app/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Kconfig b/app/Kconfig index fde08909..2fc8c45d 100644 --- a/app/Kconfig +++ b/app/Kconfig @@ -323,7 +323,7 @@ config ZMK_RGB_UNDERGLOW_SPD_START config ZMK_RGB_UNDERGLOW_EFF_START int "RGB underglow start effect int value related to the effect enum list" - range 0 3 + range 0 4 config ZMK_RGB_UNDERGLOW_ON_START bool "RGB underglow starts on by default" From 89a8f1e664d7f95dc7327454f04056a03ff4392e Mon Sep 17 00:00:00 2001 From: afiqzudinhadi Date: Thu, 25 Jun 2026 14:55:25 +0800 Subject: [PATCH 23/25] fix: disable fade animation in layer indicator effect MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The effect_layer() tick handler was fading all pixels to black every frame, then calling transient_off() which killed RGB entirely. Layer colors are static — set by set_layer() on layer change events. No per-frame animation needed. --- app/src/rgb_underglow.c | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/app/src/rgb_underglow.c b/app/src/rgb_underglow.c index 24ffd875..d92cd105 100644 --- a/app/src/rgb_underglow.c +++ b/app/src/rgb_underglow.c @@ -212,20 +212,8 @@ static void zmk_rgb_underglow_effect_swirl(void) { } static void zmk_rgb_underglow_effect_layer(void) { - bool active = false; - for (int i = 0; i < STRIP_NUM_PIXELS; i++) { - pixels[i].r -= state.animation_speed < pixels[i].r ? state.animation_speed : pixels[i].r; - pixels[i].g -= state.animation_speed < pixels[i].g ? state.animation_speed : pixels[i].g; - pixels[i].b -= state.animation_speed < pixels[i].b ? state.animation_speed : pixels[i].b; - if (pixels[i].r || pixels[i].g || pixels[i].b) { - active = true; - } - } - state.animation_step += state.animation_speed; - - if (state.animation_step > 255 || !active) { - zmk_rgb_underglow_transient_off(); - } + // Layer indicator colors are static — set by set_layer() on layer change. + // No per-frame animation needed. } static int zmk_led_generate_status(void); From d94d1597c14246b4817055b94179d281b6fefb57 Mon Sep 17 00:00:00 2001 From: afiqzudinhadi Date: Thu, 25 Jun 2026 15:32:31 +0800 Subject: [PATCH 24/25] fix: paint layer colors on effect entry and RGB toggle - select_effect: clear pixels + paint current layer when entering effect #4 - select_effect: restart tick timer when leaving effect #4 for normal effects - on: paint current layer when toggling RGB on while on effect #4 Fixes: underglow stuck on old colors, frozen display until layer switch, cycling effects requiring off/on toggle. --- app/src/rgb_underglow.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/app/src/rgb_underglow.c b/app/src/rgb_underglow.c index d92cd105..d0b86f18 100644 --- a/app/src/rgb_underglow.c +++ b/app/src/rgb_underglow.c @@ -625,6 +625,8 @@ int zmk_rgb_underglow_on(void) { #if IS_ENABLED(UNDERGLOW_LAYER_ENABLED) if (state.current_effect == UNDERGLOW_EFFECT_LAYER_INDICATORS) { state.layer_enabled = true; + memset(pixels, 0, sizeof(struct led_rgb) * STRIP_NUM_PIXELS); + zmk_rgb_underglow_set_layer(rgb_underglow_top_layer(), false); } #endif return zmk_rgb_underglow_save_state(); @@ -683,10 +685,21 @@ int zmk_rgb_underglow_select_effect(int effect) { return -EINVAL; } +#if IS_ENABLED(UNDERGLOW_LAYER_ENABLED) + bool was_layer = state.layer_enabled; +#endif + state.current_effect = effect; state.animation_step = 0; + #if IS_ENABLED(UNDERGLOW_LAYER_ENABLED) state.layer_enabled = (effect == UNDERGLOW_EFFECT_LAYER_INDICATORS); + if (state.layer_enabled && state.on) { + memset(pixels, 0, sizeof(struct led_rgb) * STRIP_NUM_PIXELS); + zmk_rgb_underglow_set_layer(rgb_underglow_top_layer(), false); + } else if (was_layer && !state.layer_enabled && state.on) { + k_timer_start(&underglow_tick, K_NO_WAIT, K_MSEC(25)); + } #endif return zmk_rgb_underglow_save_state(); } From 8f3c4d3929a6087f1cbf601d9e6b3b33a52347b1 Mon Sep 17 00:00:00 2001 From: afiqzudinhadi Date: Wed, 1 Jul 2026 14:29:24 +0800 Subject: [PATCH 25/25] refactor: modular RGB effect registration API Replace hardcoded effect enum/switch-case with STRUCT_SECTION_ITERABLE registration. Effects register via ZMK_RGB_EFFECT_DEFINE macro. Core API (rgb_effect.h): - zmk_rgb_effect_ctx passed to render callbacks - zmk_rgb_request_refresh() for static effects - ZMK_RGB_EFFECT_STATIC flag skips animation timer - on_select/on_deselect lifecycle hooks - zmk_rgb_hsb_to_rgb/scale helpers now public Built-in effects (solid/breathe/spectrum/swirl) use same API. Per-key layer RGB, behaviors, DTS bindings extracted to external zmk-rgb-effects module. --- app/CMakeLists.txt | 8 +- app/Kconfig | 8 +- .../zmk,behavior-underglow-battery.yaml | 12 - .../zmk,behavior-underglow-color.yaml | 8 - .../zmk,behavior-underglow-indicators.yaml | 13 - app/dts/bindings/zmk,underglow-layer.yaml | 24 -- app/include/linker/zmk-rgb-effects.ld | 9 + .../zmk/events/underglow_color_changed.h | 16 - app/include/zmk/rgb_effect.h | 47 +++ app/include/zmk/rgb_underglow_layer.h | 31 -- .../behaviors/behavior_underglow_battery.c | 77 ---- app/src/behaviors/behavior_underglow_color.c | 39 -- .../behaviors/behavior_underglow_indicators.c | 80 ---- app/src/events/underglow_color_changed.c | 10 - app/src/rgb_underglow.c | 399 +++++++----------- app/src/rgb_underglow_layer.c | 84 ---- 16 files changed, 209 insertions(+), 656 deletions(-) delete mode 100644 app/dts/bindings/behaviors/zmk,behavior-underglow-battery.yaml delete mode 100644 app/dts/bindings/behaviors/zmk,behavior-underglow-color.yaml delete mode 100644 app/dts/bindings/behaviors/zmk,behavior-underglow-indicators.yaml delete mode 100644 app/dts/bindings/zmk,underglow-layer.yaml create mode 100644 app/include/linker/zmk-rgb-effects.ld delete mode 100644 app/include/zmk/events/underglow_color_changed.h create mode 100644 app/include/zmk/rgb_effect.h delete mode 100644 app/include/zmk/rgb_underglow_layer.h delete mode 100644 app/src/behaviors/behavior_underglow_battery.c delete mode 100644 app/src/behaviors/behavior_underglow_color.c delete mode 100644 app/src/behaviors/behavior_underglow_indicators.c delete mode 100644 app/src/events/underglow_color_changed.c delete mode 100644 app/src/rgb_underglow_layer.c diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt index e062c2b6..478c325b 100644 --- a/app/CMakeLists.txt +++ b/app/CMakeLists.txt @@ -11,6 +11,9 @@ project(zmk) zephyr_linker_sources(SECTIONS include/linker/zmk-behaviors.ld) zephyr_linker_sources(RODATA include/linker/zmk-events.ld) +if(CONFIG_ZMK_RGB_UNDERGLOW) + zephyr_linker_sources(SECTIONS include/linker/zmk-rgb-effects.ld) +endif() if(CONFIG_ZMK_BEHAVIOR_LOCAL_IDS) zephyr_linker_sources(DATA_SECTIONS include/linker/zmk-behavior-local-id-map.ld) @@ -84,10 +87,6 @@ if ((NOT CONFIG_ZMK_SPLIT) OR CONFIG_ZMK_SPLIT_ROLE_CENTRAL) endif() target_sources_ifdef(CONFIG_ZMK_RGB_UNDERGLOW app PRIVATE src/behaviors/behavior_rgb_underglow.c) -target_sources_ifdef(CONFIG_ZMK_RGB_UNDERGLOW app PRIVATE src/behaviors/behavior_underglow_color.c) -target_sources_ifdef(CONFIG_ZMK_RGB_UNDERGLOW app PRIVATE src/behaviors/behavior_underglow_indicators.c) -target_sources_ifdef(CONFIG_ZMK_RGB_UNDERGLOW app PRIVATE src/behaviors/behavior_underglow_battery.c) -target_sources_ifdef(CONFIG_ZMK_RGB_UNDERGLOW app PRIVATE src/events/underglow_color_changed.c) target_sources_ifdef(CONFIG_ZMK_BACKLIGHT app PRIVATE src/behaviors/behavior_backlight.c) target_sources_ifdef(CONFIG_ZMK_BATTERY_REPORTING app PRIVATE src/events/battery_state_changed.c) @@ -109,7 +108,6 @@ add_subdirectory_ifdef(CONFIG_ZMK_SPLIT src/split) target_sources_ifdef(CONFIG_USB_DEVICE_STACK app PRIVATE src/usb.c) target_sources_ifdef(CONFIG_ZMK_USB app PRIVATE src/usb_hid.c) target_sources_ifdef(CONFIG_ZMK_RGB_UNDERGLOW app PRIVATE src/rgb_underglow.c) -target_sources_ifdef(CONFIG_ZMK_RGB_UNDERGLOW app PRIVATE src/rgb_underglow_layer.c) target_sources_ifdef(CONFIG_ZMK_BACKLIGHT app PRIVATE src/backlight.c) target_sources_ifdef(CONFIG_ZMK_LOW_PRIORITY_WORK_QUEUE app PRIVATE src/workqueue.c) target_sources(app PRIVATE src/main.c) diff --git a/app/Kconfig b/app/Kconfig index 2fc8c45d..fc49414f 100644 --- a/app/Kconfig +++ b/app/Kconfig @@ -322,8 +322,8 @@ config ZMK_RGB_UNDERGLOW_SPD_START range 1 5 config ZMK_RGB_UNDERGLOW_EFF_START - int "RGB underglow start effect int value related to the effect enum list" - range 0 4 + int "RGB underglow start effect index (clamped to available effects at runtime)" + range 0 15 config ZMK_RGB_UNDERGLOW_ON_START bool "RGB underglow starts on by default" @@ -335,10 +335,6 @@ config ZMK_RGB_UNDERGLOW_AUTO_OFF_USB bool "Turn off RGB underglow when USB is disconnected" depends on USB_DEVICE_STACK -config EXPERIMENTAL_RGB_LAYER - bool "Experimental per-key per-layer RGB underglow" - default n - endif # ZMK_RGB_UNDERGLOW menuconfig ZMK_BACKLIGHT diff --git a/app/dts/bindings/behaviors/zmk,behavior-underglow-battery.yaml b/app/dts/bindings/behaviors/zmk,behavior-underglow-battery.yaml deleted file mode 100644 index 4c535ea5..00000000 --- a/app/dts/bindings/behaviors/zmk,behavior-underglow-battery.yaml +++ /dev/null @@ -1,12 +0,0 @@ -# Copyright (c) 2024, The ZMK Contributors -# SPDX-License-Identifier: MIT - -description: Set underglow color based on battery level - -compatible: "zmk,behavior-underglow-battery" - -include: two_param.yaml - -properties: - threshold: - type: int diff --git a/app/dts/bindings/behaviors/zmk,behavior-underglow-color.yaml b/app/dts/bindings/behaviors/zmk,behavior-underglow-color.yaml deleted file mode 100644 index b3e90278..00000000 --- a/app/dts/bindings/behaviors/zmk,behavior-underglow-color.yaml +++ /dev/null @@ -1,8 +0,0 @@ -# Copyright (c) 2024, The ZMK Contributors -# SPDX-License-Identifier: MIT - -description: Set underglow to specified color - -compatible: "zmk,behavior-underglow-color" - -include: one_param.yaml diff --git a/app/dts/bindings/behaviors/zmk,behavior-underglow-indicators.yaml b/app/dts/bindings/behaviors/zmk,behavior-underglow-indicators.yaml deleted file mode 100644 index 553bb6c8..00000000 --- a/app/dts/bindings/behaviors/zmk,behavior-underglow-indicators.yaml +++ /dev/null @@ -1,13 +0,0 @@ -# Copyright (c) 2024, The ZMK Contributors -# SPDX-License-Identifier: MIT - -description: Set underglow for num lock indicator - -compatible: "zmk,behavior-underglow-indicators" - -include: two_param.yaml - -properties: - indicator: - type: int - default: 0 diff --git a/app/dts/bindings/zmk,underglow-layer.yaml b/app/dts/bindings/zmk,underglow-layer.yaml deleted file mode 100644 index 28221039..00000000 --- a/app/dts/bindings/zmk,underglow-layer.yaml +++ /dev/null @@ -1,24 +0,0 @@ -description: | - Allows defining a rgbmap composed of multiple layers - -compatible: "zmk,underglow-layer" - -properties: - pixel-lookup: - type: array - required: true - -child-binding: - description: "A layer to be used in a rgbmap" - - properties: - bindings: - type: phandle-array - required: true - layer-id: - type: int - required: true - fade-delay: - type: int - required: false - default: -1 diff --git a/app/include/linker/zmk-rgb-effects.ld b/app/include/linker/zmk-rgb-effects.ld new file mode 100644 index 00000000..28d519be --- /dev/null +++ b/app/include/linker/zmk-rgb-effects.ld @@ -0,0 +1,9 @@ +/* + * Copyright (c) 2024 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#include + +ITERABLE_SECTION_ROM(zmk_rgb_effect, 4) diff --git a/app/include/zmk/events/underglow_color_changed.h b/app/include/zmk/events/underglow_color_changed.h deleted file mode 100644 index 0d0ddf37..00000000 --- a/app/include/zmk/events/underglow_color_changed.h +++ /dev/null @@ -1,16 +0,0 @@ -/* - * Copyright (c) 2024 The ZMK Contributors - * - * SPDX-License-Identifier: MIT - */ - -#pragma once - -#include - -struct zmk_underglow_color_changed { - uint32_t layers; - bool wakeup; -}; - -ZMK_EVENT_DECLARE(zmk_underglow_color_changed); diff --git a/app/include/zmk/rgb_effect.h b/app/include/zmk/rgb_effect.h new file mode 100644 index 00000000..98a57b8e --- /dev/null +++ b/app/include/zmk/rgb_effect.h @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2024 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#pragma once + +#include +#include + +struct zmk_rgb_effect_ctx { + struct led_rgb *pixels; + uint16_t num_pixels; + struct zmk_led_hsb base_color; + uint16_t *animation_step; + uint8_t animation_speed; +}; + +typedef void (*zmk_rgb_effect_render_fn)(struct zmk_rgb_effect_ctx *ctx); +typedef void (*zmk_rgb_effect_event_fn)(void); + +#define ZMK_RGB_EFFECT_STATIC BIT(0) + +struct zmk_rgb_effect { + const char *name; + zmk_rgb_effect_render_fn render; + zmk_rgb_effect_event_fn on_select; + zmk_rgb_effect_event_fn on_deselect; + uint8_t flags; +}; + +#define ZMK_RGB_EFFECT_DEFINE(_sym, _name_str, _render, _flags, _on_select, _on_deselect) \ + STRUCT_SECTION_ITERABLE(zmk_rgb_effect, _sym) = { \ + .name = _name_str, \ + .render = _render, \ + .flags = _flags, \ + .on_select = _on_select, \ + .on_deselect = _on_deselect, \ + } + +void zmk_rgb_request_refresh(void); +int zmk_rgb_effect_get_count(void); + +struct led_rgb zmk_rgb_hsb_to_rgb(struct zmk_led_hsb hsb); +struct zmk_led_hsb zmk_rgb_hsb_scale_min_max(struct zmk_led_hsb hsb); +struct zmk_led_hsb zmk_rgb_hsb_scale_zero_max(struct zmk_led_hsb hsb); diff --git a/app/include/zmk/rgb_underglow_layer.h b/app/include/zmk/rgb_underglow_layer.h deleted file mode 100644 index 1b14359e..00000000 --- a/app/include/zmk/rgb_underglow_layer.h +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright (c) 2020 The ZMK Contributors - * - * SPDX-License-Identifier: MIT - */ - -#pragma once -#include - -#define ZMK_RGB_CHILD_LEN_PLUS_ONE(node) 1 + - -#define ZMK_RGBMAP_LAYERS_LEN \ - (DT_FOREACH_CHILD(DT_INST(0, zmk_underglow_layer), ZMK_RGB_CHILD_LEN_PLUS_ONE) 0) - -#define ZMK_RGBMAP_EXTRACT_BINDING(idx, drv_inst) \ - { \ - .behavior_dev = DEVICE_DT_NAME(DT_PHANDLE_BY_IDX(drv_inst, bindings, idx)), \ - .param1 = COND_CODE_0(DT_PHA_HAS_CELL_AT_IDX(drv_inst, bindings, idx, param1), (0), \ - (DT_PHA_BY_IDX(drv_inst, bindings, idx, param1))), \ - .param2 = COND_CODE_0(DT_PHA_HAS_CELL_AT_IDX(drv_inst, bindings, idx, param2), (0), \ - (DT_PHA_BY_IDX(drv_inst, bindings, idx, param2))), \ - } - -const int rgb_pixel_lookup(int idx); -const int zmk_rgbmap_id(uint8_t layer); -const int zmk_rgbmap_fade_delay(uint8_t layer); - -const struct zmk_behavior_binding *rgb_underglow_get_bindings(uint8_t layer); - -uint8_t rgb_underglow_top_layer_with_state(uint32_t state_to_test); -uint8_t rgb_underglow_top_layer(void); \ No newline at end of file diff --git a/app/src/behaviors/behavior_underglow_battery.c b/app/src/behaviors/behavior_underglow_battery.c deleted file mode 100644 index e369f842..00000000 --- a/app/src/behaviors/behavior_underglow_battery.c +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright (c) 2024 The ZMK Contributors - * - * SPDX-License-Identifier: MIT - */ - -#define DT_DRV_COMPAT zmk_behavior_underglow_battery - -// Dependencies -#include -#include -#include -#include -#include -#include -#include - -LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); - -#if DT_HAS_COMPAT_STATUS_OKAY(DT_DRV_COMPAT) - -struct underglow_battery_data { - uint32_t layers; -}; - -struct underglow_battery_config { - int threshold; -}; - -static struct underglow_battery_data underglow_battery_data = {.layers = 0}; - -static int underglow_battery_init(const struct device *dev) { return 0; }; - -static int underglow_battery_process(struct zmk_behavior_binding *binding, - struct zmk_behavior_binding_event event) { - const struct device *dev = zmk_behavior_get_binding(binding->behavior_dev); - const struct underglow_battery_config *config = dev->config; - struct underglow_battery_data *data = dev->data; - data->layers |= BIT(event.layer); - int bat = zmk_battery_state_of_charge(); - - if (bat >= config->threshold) - return binding->param2; - else - return binding->param1; -} - -static const struct behavior_driver_api underglow_battery_driver_api = { - .binding_pressed = underglow_battery_process, - .locality = BEHAVIOR_LOCALITY_GLOBAL, -#if IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA) - .get_parameter_metadata = zmk_behavior_get_empty_param_metadata, -#endif // IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA) -}; - -static int underglow_battery_listener(const zmk_event_t *eh); - -ZMK_LISTENER(behavior_underglow_battery, underglow_battery_listener); -ZMK_SUBSCRIPTION(behavior_underglow_battery, zmk_battery_state_changed); - -static int underglow_battery_listener(const zmk_event_t *eh) { - raise_zmk_underglow_color_changed((struct zmk_underglow_color_changed){ - .layers = underglow_battery_data.layers, .wakeup = false}); - - return ZMK_EV_EVENT_BUBBLE; -} - -#define KP_INST(n) \ - static struct underglow_battery_config underglow_battery_config_##n = { \ - .threshold = DT_INST_PROP(n, threshold)}; \ - BEHAVIOR_DT_INST_DEFINE(n, underglow_battery_init, NULL, &underglow_battery_data, \ - &underglow_battery_config_##n, POST_KERNEL, \ - CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, &underglow_battery_driver_api); - -DT_INST_FOREACH_STATUS_OKAY(KP_INST) - -#endif /* DT_HAS_COMPAT_STATUS_OKAY(DT_DRV_COMPAT) */ diff --git a/app/src/behaviors/behavior_underglow_color.c b/app/src/behaviors/behavior_underglow_color.c deleted file mode 100644 index 29000aab..00000000 --- a/app/src/behaviors/behavior_underglow_color.c +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (c) 2024 The ZMK Contributors - * - * SPDX-License-Identifier: MIT - */ - -#define DT_DRV_COMPAT zmk_behavior_underglow_color - -// Dependencies -#include -#include -#include - -LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); - -#if DT_HAS_COMPAT_STATUS_OKAY(DT_DRV_COMPAT) - -// Initialization Function -static int underglow_color_init(const struct device *dev) { return 0; }; - -static int underglow_color_process(struct zmk_behavior_binding *binding, - struct zmk_behavior_binding_event event) { - return binding->param1; -} - -// API Structure -static const struct behavior_driver_api underglow_color_driver_api = { - .binding_pressed = underglow_color_process, - .locality = BEHAVIOR_LOCALITY_GLOBAL, -#if IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA) - .get_parameter_metadata = zmk_behavior_get_empty_param_metadata, -#endif // IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA) - -}; - -BEHAVIOR_DT_INST_DEFINE(0, underglow_color_init, NULL, NULL, NULL, POST_KERNEL, - CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, &underglow_color_driver_api); - -#endif /* DT_HAS_COMPAT_STATUS_OKAY(DT_DRV_COMPAT) */ diff --git a/app/src/behaviors/behavior_underglow_indicators.c b/app/src/behaviors/behavior_underglow_indicators.c deleted file mode 100644 index 92ca2519..00000000 --- a/app/src/behaviors/behavior_underglow_indicators.c +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright (c) 2024 The ZMK Contributors - * - * SPDX-License-Identifier: MIT - */ - -#define DT_DRV_COMPAT zmk_behavior_underglow_indicators - -// Dependencies -#include -#include -#include -#include -#include -#include -#include - -LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); - -#if DT_HAS_COMPAT_STATUS_OKAY(DT_DRV_COMPAT) - -struct underglow_indicators_data { - zmk_hid_indicators_t indicators; - uint32_t layers; -}; - -struct underglow_indicators_config { - int indicator; -}; - -static int underglow_indicators_init(const struct device *dev) { return 0; }; - -static int underglow_indicators_process(struct zmk_behavior_binding *binding, - struct zmk_behavior_binding_event event) { - const struct device *dev = zmk_behavior_get_binding(binding->behavior_dev); - struct underglow_indicators_data *data = dev->data; - const struct underglow_indicators_config *config = dev->config; - data->layers |= BIT(event.layer); - - if (data->indicators & BIT(config->indicator)) - return binding->param2; - else - return binding->param1; -} - -static const struct behavior_driver_api underglow_indicators_driver_api = { - .binding_pressed = underglow_indicators_process, - .locality = BEHAVIOR_LOCALITY_GLOBAL, -#if IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA) - .get_parameter_metadata = zmk_behavior_get_empty_param_metadata, -#endif // IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA) -}; - -static int underglow_indicators_listener(const zmk_event_t *eh); - -ZMK_LISTENER(behavior_underglow_indicators, underglow_indicators_listener); -ZMK_SUBSCRIPTION(behavior_underglow_indicators, zmk_hid_indicators_changed); - -static struct underglow_indicators_data underglow_indicators_data = {.indicators = 0, .layers = 0}; - -static int underglow_indicators_listener(const zmk_event_t *eh) { - const struct zmk_hid_indicators_changed *ev = as_zmk_hid_indicators_changed(eh); - underglow_indicators_data.indicators = ev->indicators; - raise_zmk_underglow_color_changed((struct zmk_underglow_color_changed){ - .layers = underglow_indicators_data.layers, .wakeup = true}); - - return ZMK_EV_EVENT_BUBBLE; -} - -#define KP_INST(n) \ - static struct underglow_indicators_config underglow_indicators_config_##n = { \ - .indicator = DT_INST_PROP(n, indicator)}; \ - BEHAVIOR_DT_INST_DEFINE(n, underglow_indicators_init, NULL, &underglow_indicators_data, \ - &underglow_indicators_config_##n, POST_KERNEL, \ - CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, \ - &underglow_indicators_driver_api); - -DT_INST_FOREACH_STATUS_OKAY(KP_INST) - -#endif /* DT_HAS_COMPAT_STATUS_OKAY(DT_DRV_COMPAT) */ diff --git a/app/src/events/underglow_color_changed.c b/app/src/events/underglow_color_changed.c deleted file mode 100644 index c00bdc6b..00000000 --- a/app/src/events/underglow_color_changed.c +++ /dev/null @@ -1,10 +0,0 @@ -/* - * Copyright (c) 2020 The ZMK Contributors - * - * SPDX-License-Identifier: MIT - */ - -#include -#include - -ZMK_EVENT_IMPL(zmk_underglow_color_changed); \ No newline at end of file diff --git a/app/src/rgb_underglow.c b/app/src/rgb_underglow.c index d0b86f18..567497c9 100644 --- a/app/src/rgb_underglow.c +++ b/app/src/rgb_underglow.c @@ -29,25 +29,19 @@ #include #include -#include +#include #include #include #include #include -#include #include -#include #if IS_ENABLED(CONFIG_ZMK_SPLIT_BLE_CENTRAL_BATTERY_LEVEL_FETCHING) #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) @@ -59,11 +53,6 @@ LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); #define STRIP_CHOSEN DT_CHOSEN(zmk_underglow) #define STRIP_NUM_PIXELS DT_PROP(STRIP_CHOSEN, chain_length) -#if DT_HAS_COMPAT_STATUS_OKAY(zmk_underglow_layer) && IS_ENABLED(CONFIG_EXPERIMENTAL_RGB_LAYER) -#define UNDERGLOW_LAYER_ENABLED 1 -static void zmk_rgb_underglow_set_layer(uint8_t layer, bool wakeup); -#endif - #define HUE_MAX 360 #define SAT_MAX 100 #define BRT_MAX 100 @@ -71,17 +60,6 @@ static void zmk_rgb_underglow_set_layer(uint8_t layer, bool wakeup); BUILD_ASSERT(CONFIG_ZMK_RGB_UNDERGLOW_BRT_MIN <= CONFIG_ZMK_RGB_UNDERGLOW_BRT_MAX, "ERROR: RGB underglow maximum brightness is less than minimum brightness"); -enum rgb_underglow_effect { - UNDERGLOW_EFFECT_SOLID, - UNDERGLOW_EFFECT_BREATHE, - UNDERGLOW_EFFECT_SPECTRUM, - UNDERGLOW_EFFECT_SWIRL, -#if IS_ENABLED(UNDERGLOW_LAYER_ENABLED) - UNDERGLOW_EFFECT_LAYER_INDICATORS, -#endif - UNDERGLOW_EFFECT_NUMBER // Used to track number of underglow effects -}; - struct rgb_underglow_state { struct zmk_led_hsb color; uint8_t animation_speed; @@ -89,7 +67,6 @@ struct rgb_underglow_state { uint16_t animation_step; bool on; bool status_active; - bool layer_enabled; uint16_t status_animation_step; }; @@ -106,18 +83,20 @@ static const struct device *const ext_power = DEVICE_DT_GET(DT_INST(0, zmk_ext_p void zmk_rgb_set_ext_power(void); -static struct zmk_led_hsb hsb_scale_min_max(struct zmk_led_hsb hsb) { +/* --- Public utility functions for effects --- */ + +struct zmk_led_hsb zmk_rgb_hsb_scale_min_max(struct zmk_led_hsb hsb) { hsb.b = CONFIG_ZMK_RGB_UNDERGLOW_BRT_MIN + (CONFIG_ZMK_RGB_UNDERGLOW_BRT_MAX - CONFIG_ZMK_RGB_UNDERGLOW_BRT_MIN) * hsb.b / BRT_MAX; return hsb; } -static struct zmk_led_hsb hsb_scale_zero_max(struct zmk_led_hsb hsb) { +struct zmk_led_hsb zmk_rgb_hsb_scale_zero_max(struct zmk_led_hsb hsb) { hsb.b = hsb.b * CONFIG_ZMK_RGB_UNDERGLOW_BRT_MAX / BRT_MAX; return hsb; } -static struct led_rgb hsb_to_rgb(struct zmk_led_hsb hsb) { +struct led_rgb zmk_rgb_hsb_to_rgb(struct zmk_led_hsb hsb) { float r = 0, g = 0, b = 0; uint8_t i = hsb.h / 60; @@ -166,56 +145,78 @@ static struct led_rgb hsb_to_rgb(struct zmk_led_hsb hsb) { return rgb; } -static void zmk_rgb_underglow_effect_solid(void) { - for (int i = 0; i < STRIP_NUM_PIXELS; i++) { - pixels[i] = hsb_to_rgb(hsb_scale_min_max(state.color)); +/* --- Built-in effects --- */ + +static void effect_solid_render(struct zmk_rgb_effect_ctx *ctx) { + for (int i = 0; i < ctx->num_pixels; i++) { + ctx->pixels[i] = zmk_rgb_hsb_to_rgb(zmk_rgb_hsb_scale_min_max(ctx->base_color)); } } -static void zmk_rgb_underglow_effect_breathe(void) { - for (int i = 0; i < STRIP_NUM_PIXELS; i++) { - struct zmk_led_hsb hsb = state.color; - hsb.b = abs(state.animation_step - 1200) / 12; +static void effect_breathe_render(struct zmk_rgb_effect_ctx *ctx) { + for (int i = 0; i < ctx->num_pixels; i++) { + struct zmk_led_hsb hsb = ctx->base_color; + hsb.b = abs(*ctx->animation_step - 1200) / 12; - pixels[i] = hsb_to_rgb(hsb_scale_zero_max(hsb)); + ctx->pixels[i] = zmk_rgb_hsb_to_rgb(zmk_rgb_hsb_scale_zero_max(hsb)); } - state.animation_step += state.animation_speed * 10; + *ctx->animation_step += ctx->animation_speed * 10; - if (state.animation_step > 2400) { - state.animation_step = 0; + if (*ctx->animation_step > 2400) { + *ctx->animation_step = 0; } } -static void zmk_rgb_underglow_effect_spectrum(void) { - for (int i = 0; i < STRIP_NUM_PIXELS; i++) { - struct zmk_led_hsb hsb = state.color; - hsb.h = state.animation_step; +static void effect_spectrum_render(struct zmk_rgb_effect_ctx *ctx) { + for (int i = 0; i < ctx->num_pixels; i++) { + struct zmk_led_hsb hsb = ctx->base_color; + hsb.h = *ctx->animation_step; - pixels[i] = hsb_to_rgb(hsb_scale_min_max(hsb)); + ctx->pixels[i] = zmk_rgb_hsb_to_rgb(zmk_rgb_hsb_scale_min_max(hsb)); } - state.animation_step += state.animation_speed; - state.animation_step = state.animation_step % HUE_MAX; + *ctx->animation_step += ctx->animation_speed; + *ctx->animation_step = *ctx->animation_step % HUE_MAX; } -static void zmk_rgb_underglow_effect_swirl(void) { - for (int i = 0; i < STRIP_NUM_PIXELS; i++) { - struct zmk_led_hsb hsb = state.color; - hsb.h = (HUE_MAX / STRIP_NUM_PIXELS * i + state.animation_step) % HUE_MAX; +static void effect_swirl_render(struct zmk_rgb_effect_ctx *ctx) { + for (int i = 0; i < ctx->num_pixels; i++) { + struct zmk_led_hsb hsb = ctx->base_color; + hsb.h = (HUE_MAX / ctx->num_pixels * i + *ctx->animation_step) % HUE_MAX; - pixels[i] = hsb_to_rgb(hsb_scale_min_max(hsb)); + ctx->pixels[i] = zmk_rgb_hsb_to_rgb(zmk_rgb_hsb_scale_min_max(hsb)); } - state.animation_step += state.animation_speed * 2; - state.animation_step = state.animation_step % HUE_MAX; + *ctx->animation_step += ctx->animation_speed * 2; + *ctx->animation_step = *ctx->animation_step % HUE_MAX; } -static void zmk_rgb_underglow_effect_layer(void) { - // Layer indicator colors are static — set by set_layer() on layer change. - // No per-frame animation needed. +ZMK_RGB_EFFECT_DEFINE(effect_solid, "Solid", effect_solid_render, 0, NULL, NULL); +ZMK_RGB_EFFECT_DEFINE(effect_breathe, "Breathe", effect_breathe_render, 0, NULL, NULL); +ZMK_RGB_EFFECT_DEFINE(effect_spectrum, "Spectrum", effect_spectrum_render, 0, NULL, NULL); +ZMK_RGB_EFFECT_DEFINE(effect_swirl, "Swirl", effect_swirl_render, 0, NULL, NULL); + +/* --- Effect registry helpers --- */ + +int zmk_rgb_effect_get_count(void) { + int count; + STRUCT_SECTION_COUNT(zmk_rgb_effect, &count); + return count; } +static struct zmk_rgb_effect *zmk_rgb_effect_get(int index) { + int count = zmk_rgb_effect_get_count(); + if (index < 0 || index >= count) { + return NULL; + } + struct zmk_rgb_effect *effect; + STRUCT_SECTION_GET(zmk_rgb_effect, index, &effect); + return effect; +} + +/* --- Status indicator overlay (separate from effects) --- */ + static int zmk_led_generate_status(void); static void zmk_led_write_pixels(void) { @@ -234,19 +235,16 @@ static void zmk_led_write_pixels(void) { blend = zmk_led_generate_status(); } - // fast path: no status indicators, battery level OK if (blend == 0 && bat0 >= 20) { led_strip_update_rgb(led_strip, pixels, STRIP_NUM_PIXELS); return; } - // battery below minimum charge if (bat0 < 10) { memset(pixels, 0, sizeof(struct led_rgb) * STRIP_NUM_PIXELS); #if IS_ENABLED(CONFIG_ZMK_RGB_UNDERGLOW_EXT_POWER) if (state.on) { int c_power = ext_power_get(ext_power); if (c_power && !state.status_active) { - // power is on, RGB underglow is on, but battery is too low state.on = false; reset_ext_power = true; } @@ -275,7 +273,6 @@ static void zmk_led_write_pixels(void) { } } - // battery below 20%, reduce LED brightness if (bat0 < 20) { for (int i = 0; i < STRIP_NUM_PIXELS; i++) { led_buffer[i].r = led_buffer[i].r >> 1; @@ -338,8 +335,6 @@ static void zmk_led_battery_level(int bat_level, const uint8_t *addresses, size_ bat_colour = red; } - // originally, six levels, 0 .. 100 - for (int i = 0; i < addresses_len; i++) { int min_level = (i * 100) / (addresses_len - 1); if (bat_level >= min_level) { @@ -363,7 +358,6 @@ static int zmk_led_generate_status(void) { status_pixels[i] = (struct led_rgb){r : 0, g : 0, b : 0}; } - // BATTERY STATUS #if IS_ENABLED(CONFIG_ZMK_BATTERY_REPORTING) zmk_led_battery_level(zmk_battery_state_of_charge(), underglow_bat_lhs, DT_PROP_LEN(UNDERGLOW_INDICATORS, bat_lhs)); @@ -379,10 +373,9 @@ static int zmk_led_generate_status(void) { } else if (rc == -EINVAL) { LOG_ERR("Invalid peripheral index requested for battery level read: 0"); } -#endif // CONFIG_ZMK_SPLIT_BLE_CENTRAL_BATTERY_LEVEL_FETCHING -#endif // CONFIG_ZMK_BATTERY_REPORTING +#endif +#endif - // CAPSLOCK/NUMLOCK/SCROLLOCK STATUS zmk_hid_indicators_t led_flags = zmk_hid_indicators_get_current_profile(); if (led_flags & ZMK_LED_CAPSLOCK_BIT) @@ -392,7 +385,6 @@ static int zmk_led_generate_status(void) { if (led_flags & ZMK_LED_SCROLLLOCK_BIT) status_pixels[DT_PROP(UNDERGLOW_INDICATORS, scrolllock)] = red; - // LAYER STATUS for (uint8_t i = 0; i < DT_PROP_LEN(UNDERGLOW_INDICATORS, layer_state); i++) { if (zmk_keymap_layer_active(i)) status_pixels[underglow_layer_state[i]] = magenta; @@ -410,13 +402,13 @@ static int zmk_led_generate_status(void) { int8_t status = zmk_ble_profile_status(i); int ble_pixel = underglow_ble_state[i]; if (status == 2 && active_endpoint.transport == ZMK_TRANSPORT_BLE && - active_ble_profile_index == i) { // connected AND active + active_ble_profile_index == i) { status_pixels[ble_pixel] = white; - } else if (status == 2) { // connected + } else if (status == 2) { status_pixels[ble_pixel] = dull_green; - } else if (status == 1) { // paired + } else if (status == 1) { status_pixels[ble_pixel] = red; - } else if (status == 0) { // unused + } else if (status == 0) { status_pixels[ble_pixel] = lilac; } } @@ -424,13 +416,13 @@ static int zmk_led_generate_status(void) { enum zmk_usb_conn_state usb_state = zmk_usb_get_conn_state(); if (usb_state == ZMK_USB_CONN_HID && - active_endpoint.transport == ZMK_TRANSPORT_USB) { // connected AND active + active_endpoint.transport == ZMK_TRANSPORT_USB) { status_pixels[DT_PROP(UNDERGLOW_INDICATORS, usb_state)] = white; - } else if (usb_state == ZMK_USB_CONN_HID) { // connected + } else if (usb_state == ZMK_USB_CONN_HID) { status_pixels[DT_PROP(UNDERGLOW_INDICATORS, usb_state)] = dull_green; - } else if (usb_state == ZMK_USB_CONN_POWERED) { // powered + } else if (usb_state == ZMK_USB_CONN_POWERED) { status_pixels[DT_PROP(UNDERGLOW_INDICATORS, usb_state)] = red; - } else if (usb_state == ZMK_USB_CONN_NONE) { // disconnected + } else if (usb_state == ZMK_USB_CONN_NONE) { status_pixels[DT_PROP(UNDERGLOW_INDICATORS, usb_state)] = lilac; } @@ -447,34 +439,21 @@ static int zmk_led_generate_status(void) { return blend; } -#endif // underglow_indicators exists +#endif -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)); -} +/* --- Tick / animation loop --- */ static void zmk_rgb_underglow_tick(struct k_work *work) { - switch (state.current_effect) { - case UNDERGLOW_EFFECT_SOLID: - zmk_rgb_underglow_effect_solid(); - break; - case UNDERGLOW_EFFECT_BREATHE: - zmk_rgb_underglow_effect_breathe(); - break; - case UNDERGLOW_EFFECT_SPECTRUM: - zmk_rgb_underglow_effect_spectrum(); - break; - case UNDERGLOW_EFFECT_SWIRL: - zmk_rgb_underglow_effect_swirl(); - break; -#if IS_ENABLED(UNDERGLOW_LAYER_ENABLED) - case UNDERGLOW_EFFECT_LAYER_INDICATORS: - zmk_rgb_underglow_effect_layer(); - break; -#endif + struct zmk_rgb_effect *effect = zmk_rgb_effect_get(state.current_effect); + if (effect && effect->render) { + struct zmk_rgb_effect_ctx ctx = { + .pixels = pixels, + .num_pixels = STRIP_NUM_PIXELS, + .base_color = state.color, + .animation_step = &state.animation_step, + .animation_speed = state.animation_speed, + }; + effect->render(&ctx); } zmk_led_write_pixels(); @@ -492,6 +471,12 @@ static void zmk_rgb_underglow_tick_handler(struct k_timer *timer) { K_TIMER_DEFINE(underglow_tick, zmk_rgb_underglow_tick_handler, NULL); +void zmk_rgb_request_refresh(void) { + k_work_submit_to_queue(zmk_workqueue_lowprio_work_q(), &underglow_tick_work); +} + +/* --- Settings persistence --- */ + #if IS_ENABLED(CONFIG_SETTINGS) static int rgb_settings_set(const char *name, size_t len, settings_read_cb read_cb, void *cb_arg) { const char *next; @@ -503,14 +488,13 @@ static int rgb_settings_set(const char *name, size_t len, settings_read_cb read_ rc = read_cb(cb_arg, &state, sizeof(state)); if (rc >= 0) { + int effect_count = zmk_rgb_effect_get_count(); + if (state.current_effect >= effect_count) { + state.current_effect = 0; + } if (state.on) { k_timer_start(&underglow_tick, K_NO_WAIT, K_MSEC(50)); } -#if IS_ENABLED(UNDERGLOW_LAYER_ENABLED) - if (state.layer_enabled) { - zmk_rgb_underglow_set_layer(rgb_underglow_top_layer(), true); - } -#endif return 0; } @@ -558,14 +542,20 @@ static int zmk_rgb_underglow_init(void) { state.on = zmk_usb_is_powered(); #endif + int effect_count = zmk_rgb_effect_get_count(); + if (state.current_effect >= effect_count && effect_count > 0) { + state.current_effect = 0; + } + if (state.on) { - k_timer_start(&underglow_tick, K_NO_WAIT, K_MSEC(25)); + struct zmk_rgb_effect *effect = zmk_rgb_effect_get(state.current_effect); + if (effect && (effect->flags & ZMK_RGB_EFFECT_STATIC)) { + zmk_rgb_request_refresh(); + } else { + k_timer_start(&underglow_tick, K_NO_WAIT, K_MSEC(25)); + } } -#if IS_ENABLED(UNDERGLOW_LAYER_ENABLED) - if (state.layer_enabled) { - zmk_rgb_underglow_set_layer(rgb_underglow_top_layer(), true); - } -#endif + return 0; } @@ -582,7 +572,7 @@ int zmk_rgb_underglow_get_state(bool *on_off) { if (!led_strip) return -ENODEV; - *on_off = state.on || state.layer_enabled; + *on_off = state.on; return 0; } @@ -598,13 +588,12 @@ void zmk_rgb_set_ext_power(void) { int desired_state = state.on || state.status_active; #if IS_ENABLED(CONFIG_ZMK_BATTERY_REPORTING) - // force power off, when battery low (<10%) if (state.on && !state.status_active) { if (zmk_battery_state_of_charge() < 10) { desired_state = false; } } -#endif // CONFIG_ZMK_BATTERY_REPORTING +#endif if (desired_state && !c_power) { int rc = ext_power_enable(ext_power); @@ -617,18 +606,11 @@ void zmk_rgb_set_ext_power(void) { LOG_ERR("Unable to disable EXT_POWER: %d", rc); } } -#endif // CONFIG_ZMK_RGB_UNDERGLOW_EXT_POWER +#endif } int zmk_rgb_underglow_on(void) { zmk_rgb_underglow_transient_on(); -#if IS_ENABLED(UNDERGLOW_LAYER_ENABLED) - if (state.current_effect == UNDERGLOW_EFFECT_LAYER_INDICATORS) { - state.layer_enabled = true; - memset(pixels, 0, sizeof(struct led_rgb) * STRIP_NUM_PIXELS); - zmk_rgb_underglow_set_layer(rgb_underglow_top_layer(), false); - } -#endif return zmk_rgb_underglow_save_state(); } @@ -640,7 +622,16 @@ int zmk_rgb_underglow_transient_on(void) { zmk_rgb_set_ext_power(); state.animation_step = 0; - k_timer_start(&underglow_tick, K_NO_WAIT, K_MSEC(25)); + + struct zmk_rgb_effect *effect = zmk_rgb_effect_get(state.current_effect); + if (effect && effect->on_select) { + effect->on_select(); + } + if (effect && (effect->flags & ZMK_RGB_EFFECT_STATIC)) { + zmk_rgb_request_refresh(); + } else { + k_timer_start(&underglow_tick, K_NO_WAIT, K_MSEC(25)); + } return 0; } @@ -656,7 +647,6 @@ K_WORK_DEFINE(underglow_off_work, zmk_rgb_underglow_off_handler); int zmk_rgb_underglow_off(void) { zmk_rgb_underglow_transient_off(); - state.layer_enabled = false; return zmk_rgb_underglow_save_state(); } @@ -664,6 +654,11 @@ int zmk_rgb_underglow_transient_off(void) { if (!led_strip) return -ENODEV; + struct zmk_rgb_effect *effect = zmk_rgb_effect_get(state.current_effect); + if (effect && effect->on_deselect) { + effect->on_deselect(); + } + k_work_submit_to_queue(zmk_workqueue_lowprio_work_q(), &underglow_off_work); k_timer_stop(&underglow_tick); @@ -674,33 +669,43 @@ int zmk_rgb_underglow_transient_off(void) { } int zmk_rgb_underglow_calc_effect(int direction) { - return (state.current_effect + UNDERGLOW_EFFECT_NUMBER + direction) % UNDERGLOW_EFFECT_NUMBER; + int count = zmk_rgb_effect_get_count(); + if (count == 0) + return 0; + return (state.current_effect + count + direction) % count; } int zmk_rgb_underglow_select_effect(int effect) { if (!led_strip) return -ENODEV; - if (effect < 0 || effect >= UNDERGLOW_EFFECT_NUMBER) { + int count = zmk_rgb_effect_get_count(); + if (effect < 0 || effect >= count) { return -EINVAL; } -#if IS_ENABLED(UNDERGLOW_LAYER_ENABLED) - bool was_layer = state.layer_enabled; -#endif + struct zmk_rgb_effect *old_eff = zmk_rgb_effect_get(state.current_effect); + if (old_eff && old_eff->on_deselect) { + old_eff->on_deselect(); + } state.current_effect = effect; state.animation_step = 0; -#if IS_ENABLED(UNDERGLOW_LAYER_ENABLED) - state.layer_enabled = (effect == UNDERGLOW_EFFECT_LAYER_INDICATORS); - if (state.layer_enabled && state.on) { - memset(pixels, 0, sizeof(struct led_rgb) * STRIP_NUM_PIXELS); - zmk_rgb_underglow_set_layer(rgb_underglow_top_layer(), false); - } else if (was_layer && !state.layer_enabled && state.on) { - k_timer_start(&underglow_tick, K_NO_WAIT, K_MSEC(25)); + struct zmk_rgb_effect *new_eff = zmk_rgb_effect_get(state.current_effect); + if (new_eff && new_eff->on_select) { + new_eff->on_select(); } -#endif + + if (state.on) { + if (new_eff && (new_eff->flags & ZMK_RGB_EFFECT_STATIC)) { + k_timer_stop(&underglow_tick); + zmk_rgb_request_refresh(); + } else { + k_timer_start(&underglow_tick, K_NO_WAIT, K_MSEC(25)); + } + } + return zmk_rgb_underglow_save_state(); } @@ -712,81 +717,7 @@ int zmk_rgb_underglow_toggle(void) { return state.on ? zmk_rgb_underglow_off() : zmk_rgb_underglow_on(); } -#if IS_ENABLED(UNDERGLOW_LAYER_ENABLED) - -static struct led_rgb hex_to_rgb(uint8_t r, uint8_t g, uint8_t b) { - struct zmk_led_hsb hsb = state.color; - return (struct led_rgb){ - r : (hsb.b * (r)) / 0xff, - g : (hsb.b * (g)) / 0xff, - b : (hsb.b * (b)) / 0xff - }; -} - -static int zmk_rgb_underglow_apply_rgbmap(const struct zmk_behavior_binding *bindings, - size_t rgbmap_len, uint8_t layer) { - int rc = 0; - for (int i = 0; i < STRIP_NUM_PIXELS; i++) { - uint8_t midx = rgb_pixel_lookup(i); - if (midx >= ZMK_KEYMAP_LEN) { - LOG_DBG("out of range"); - } else { - const struct device *dev = zmk_behavior_get_binding(bindings[midx].behavior_dev); - - if (dev == NULL) { - continue; - } - - const struct behavior_driver_api *api = (const struct behavior_driver_api *)dev->api; - - if (api->binding_pressed == NULL) { - continue; - } - struct zmk_behavior_binding_event event = { - .position = midx, .layer = layer, .timestamp = k_uptime_get()}; - - int color = api->binding_pressed((struct zmk_behavior_binding *)&bindings[midx], event); - - if (color > 0) { - pixels[i] = - hex_to_rgb((color & 0xFF0000) >> 16, (color & 0xFF00) >> 8, color & 0xFF); - rc = 1; - } else { - pixels[i] = (struct led_rgb){r : 0, g : 0, b : 0}; - } - } - } - return rc; -} - -static void zmk_rgb_underglow_set_layer(uint8_t layer, bool wakeup) { - LOG_DBG("state.layer: %d state.on: %d", state.layer_enabled, state.on); - if (!state.layer_enabled) - return; - - const struct zmk_behavior_binding *rgbmap = rgb_underglow_get_bindings(layer); - if (rgbmap != NULL && zmk_rgb_underglow_apply_rgbmap(rgbmap, ZMK_KEYMAP_LEN, layer)) { - if (!state.on) { - if (!wakeup) { - LOG_DBG("rgb off and no wakeup, abort refresh"); - return; - } - zmk_rgb_underglow_transient_on(); - } - k_timer_stop(&underglow_tick); - state.animation_step = 0; - int fade_delay = zmk_rgbmap_fade_delay(layer); - if (fade_delay >= 0) { - k_timer_start(&underglow_tick, K_SECONDS(fade_delay), K_MSEC(50)); - } - LOG_DBG("write pixels"); - zmk_led_write_pixels(); - } else { - if (state.on) - zmk_rgb_underglow_transient_off(); - } -} -#endif /* IS_ENABLED(UNDERGLOW_LAYER_ENABLED) */ +/* --- Status overlay timer --- */ static void zmk_led_write_pixels_work(struct k_work *work); static void zmk_rgb_underglow_status_update(struct k_timer *timer); @@ -830,6 +761,8 @@ int zmk_rgb_underglow_status(void) { return 0; } +/* --- HSB control functions --- */ + int zmk_rgb_underglow_set_hsb(struct zmk_led_hsb color) { if (color.h > HUE_MAX || color.s > SAT_MAX || color.b > BRT_MAX) { return -ENOTSUP; @@ -916,8 +849,10 @@ int zmk_rgb_underglow_change_spd(int direction) { return zmk_rgb_underglow_save_state(); } +/* --- Auto off / idle listeners --- */ + #if IS_ENABLED(CONFIG_ZMK_RGB_UNDERGLOW_AUTO_OFF_IDLE) || \ - IS_ENABLED(CONFIG_ZMK_RGB_UNDERGLOW_AUTO_OFF_USB) || IS_ENABLED(UNDERGLOW_LAYER_ENABLED) + IS_ENABLED(CONFIG_ZMK_RGB_UNDERGLOW_AUTO_OFF_USB) struct rgb_underglow_sleep_state { bool is_awake; bool rgb_state_before_sleeping; @@ -929,19 +864,12 @@ static int rgb_underglow_auto_state(bool target_wake_state) { rgb_state_before_sleeping : false }; - // wake up event while awake, or sleep event while sleeping -> no-op if (target_wake_state == sleep_state.is_awake) { return 0; } sleep_state.is_awake = target_wake_state; if (sleep_state.is_awake) { -#if IS_ENABLED(UNDERGLOW_LAYER_ENABLED) - if (state.layer_enabled) { - zmk_rgb_underglow_set_layer(rgb_underglow_top_layer(), true); - return 0; - } -#endif if (sleep_state.rgb_state_before_sleeping) { return zmk_rgb_underglow_transient_on(); } else { @@ -961,30 +889,6 @@ static int rgb_underglow_event_listener(const zmk_event_t *eh) { } #endif -#if IS_ENABLED(UNDERGLOW_LAYER_ENABLED) - if (as_zmk_split_peripheral_layer_changed(eh)) { - const struct zmk_split_peripheral_layer_changed *ev = - as_zmk_split_peripheral_layer_changed(eh); - LOG_DBG("zmk_split_peripheral_layer_changed: %08x", ev->layers); -#if !IS_ENABLED(CONFIG_ZMK_SPLIT_ROLE_CENTRAL) - set_peripheral_layers_state(ev->layers); -#endif - uint8_t layer = rgb_underglow_top_layer(); - LOG_DBG("top layer: %d", layer); - zmk_rgb_underglow_set_layer(layer, true); - return 0; - } - if (as_zmk_underglow_color_changed(eh)) { - const struct zmk_underglow_color_changed *ev = as_zmk_underglow_color_changed(eh); - uint8_t layer = rgb_underglow_top_layer(); - LOG_DBG("refresh layers %d, current: %d, wakeup: %d", ev->layers, layer, ev->wakeup); - if ((ev->layers & (BIT(layer))) == BIT(layer)) { - zmk_rgb_underglow_set_layer(rgb_underglow_top_layer(), ev->wakeup); - } - return 0; - } -#endif /* UNDERGLOW_LAYER_ENABLED */ - #if IS_ENABLED(CONFIG_ZMK_RGB_UNDERGLOW_AUTO_OFF_USB) if (as_zmk_usb_conn_state_changed(eh)) { return rgb_underglow_auto_state(zmk_usb_is_powered()); @@ -995,9 +899,7 @@ static int rgb_underglow_event_listener(const zmk_event_t *eh) { } ZMK_LISTENER(rgb_underglow, rgb_underglow_event_listener); -#endif // IS_ENABLED(CONFIG_ZMK_RGB_UNDERGLOW_AUTO_OFF_IDLE) || - // IS_ENABLED(CONFIG_ZMK_RGB_UNDERGLOW_AUTO_OFF_USB) || - // IS_ENABLED(UNDERGLOW_LAYER_ENABLED) +#endif #if IS_ENABLED(CONFIG_ZMK_RGB_UNDERGLOW_AUTO_OFF_IDLE) ZMK_SUBSCRIPTION(rgb_underglow, zmk_activity_state_changed); @@ -1007,9 +909,4 @@ ZMK_SUBSCRIPTION(rgb_underglow, zmk_activity_state_changed); ZMK_SUBSCRIPTION(rgb_underglow, zmk_usb_conn_state_changed); #endif -#if IS_ENABLED(UNDERGLOW_LAYER_ENABLED) -ZMK_SUBSCRIPTION(rgb_underglow, zmk_split_peripheral_layer_changed); -ZMK_SUBSCRIPTION(rgb_underglow, zmk_underglow_color_changed); -#endif - SYS_INIT(zmk_rgb_underglow_init, APPLICATION, CONFIG_APPLICATION_INIT_PRIORITY); diff --git a/app/src/rgb_underglow_layer.c b/app/src/rgb_underglow_layer.c deleted file mode 100644 index d3fbbc6e..00000000 --- a/app/src/rgb_underglow_layer.c +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Copyright (c) 2020 The ZMK Contributors - * - * SPDX-License-Identifier: MIT - */ - -#include -#include -#include -#include -#include -LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); - -#include -#include -#include - -#if !IS_ENABLED(CONFIG_ZMK_SPLIT_ROLE_CENTRAL) -#include -#endif - -#define DT_DRV_COMPAT zmk_underglow_layer -#if DT_HAS_COMPAT_STATUS_OKAY(DT_DRV_COMPAT) - -#define UNDERGLOW_LAYER_ENABLED -#define LAYER_ID(node) DT_PROP(node, layer_id) -#define FADE_DELAY(node) DT_PROP(node, fade_delay) - -#define TRANSFORMED_RGB_LAYER(node) \ - {COND_CODE_1(DT_NODE_HAS_PROP(node, bindings), \ - (LISTIFY(DT_PROP_LEN(node, bindings), ZMK_RGBMAP_EXTRACT_BINDING, (, ), node)), \ - ())} - -#define RGBMAP_VAR(_name, _opts) \ - static _opts struct zmk_behavior_binding _name[ZMK_RGBMAP_LAYERS_LEN][ZMK_KEYMAP_LEN] = { \ - DT_INST_FOREACH_CHILD_STATUS_OKAY_SEP(0, TRANSFORMED_RGB_LAYER, (, ))}; - -RGBMAP_VAR(zmk_rgbmap, COND_CODE_1(IS_ENABLED(CONFIG_ZMK_KEYMAP_SETTINGS_STORAGE), (), (const))) - -const int pixel_lookup_table[] = DT_INST_PROP(0, pixel_lookup); - -static int zmk_rgbmap_ids[ZMK_RGBMAP_LAYERS_LEN] = {DT_INST_FOREACH_CHILD_SEP(0, LAYER_ID, (, ))}; -static int zmk_rgbmap_fds[ZMK_RGBMAP_LAYERS_LEN] = {DT_INST_FOREACH_CHILD_SEP(0, FADE_DELAY, (, ))}; - -const int rgb_pixel_lookup(int idx) { return pixel_lookup_table[idx]; }; - -const int zmk_rgbmap_id(uint8_t layer) { - for (uint8_t i = 0; i < ZMK_RGBMAP_LAYERS_LEN; i++) { - if (zmk_rgbmap_ids[i] == layer) { - return i; - } - } - return -1; -} - -const int zmk_rgbmap_fade_delay(uint8_t layer) { return zmk_rgbmap_fds[zmk_rgbmap_id(layer)]; } - -const struct zmk_behavior_binding *rgb_underglow_get_bindings(uint8_t layer) { - int rgblayer = zmk_rgbmap_id(layer); - if (rgblayer == -1) { - return NULL; - } else { - return zmk_rgbmap[rgblayer]; - } -} - -uint8_t rgb_underglow_top_layer_with_state(uint32_t state_to_test) { - for (uint8_t layer = ZMK_KEYMAP_LAYERS_LEN - 1; layer > 0; layer--) { - if ((state_to_test & (BIT(layer))) == (BIT(layer)) || layer == 0) { - return layer; - } - } - // return default layer (0) - return 0; -} - -uint8_t rgb_underglow_top_layer(void) { -#if IS_ENABLED(CONFIG_ZMK_SPLIT_ROLE_CENTRAL) - return zmk_keymap_highest_layer_active(); -#else - return peripheral_highest_layer_active(); -#endif -} -#endif /* DT_HAS_COMPAT_STATUS_OKAY(DT_DRV_COMPAT) */ \ No newline at end of file