From d45882f497949962627ca4e99beff46808d94dbb Mon Sep 17 00:00:00 2001 From: Chris Andreae Date: Sat, 28 May 2022 20:15:51 +0900 Subject: [PATCH 01/23] 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 a7a9b4f018f..b8370003d11 100644 --- a/app/src/rgb_underglow.c +++ b/app/src/rgb_underglow.c @@ -280,7 +280,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; @@ -318,7 +318,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 aca523dfce9b6447ffd19d833b791d44f1f286de Mon Sep 17 00:00:00 2001 From: Donald Gordon Date: Wed, 13 Jul 2022 22:39:44 +1200 Subject: [PATCH 02/23] 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 | 296 ++++++++++++++++-- 9 files changed, 340 insertions(+), 20 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 00000000000..d5cdde80a9a --- /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 c1a8008273c..657ac2fd528 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 773323c1cf9..a0bd588f3c7 100644 --- a/app/include/zmk/ble.h +++ b/app/include/zmk/ble.h @@ -33,6 +33,7 @@ bt_addr_le_t *zmk_ble_active_profile_addr(void); 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 70240183e36..4a2245f5185 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_MOUSE) diff --git a/app/include/zmk/rgb_underglow.h b/app/include/zmk/rgb_underglow.h index be0ef252290..0c45e1c68f7 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 a16ee591ed4..10f1ce94b08 100644 --- a/app/src/behaviors/behavior_rgb_underglow.c +++ b/app/src/behaviors/behavior_rgb_underglow.c @@ -131,6 +131,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 7e1ae7d4949..f197e106a51 100644 --- a/app/src/ble.c +++ b/app/src/ble.c @@ -129,6 +129,23 @@ bool zmk_ble_active_profile_is_connected(void) { 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 395d6ba502a..e583d78a969 100644 --- a/app/src/endpoints.c +++ b/app/src/endpoints.c @@ -302,6 +302,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) settings_subsys_init(); diff --git a/app/src/rgb_underglow.c b/app/src/rgb_underglow.c index b8370003d11..88faef09be1 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,199 @@ 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 = zmk_battery_state_of_charge(); + int blend = 0; + 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; + } + + 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); + } + } + + 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); + } +} + +#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 + 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_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 + + // 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; + + 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; + } + } + + 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 +399,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); @@ -303,20 +508,37 @@ 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 (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 +} + +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)); @@ -328,7 +550,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); @@ -337,19 +559,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(); } @@ -380,6 +594,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 46d393137a6a100178c0d2743d14db02fb6127c6 Mon Sep 17 00:00:00 2001 From: Donald Gordon Date: Wed, 28 Sep 2022 21:11:04 +0900 Subject: [PATCH 03/23] RGB underglow: dim or disable when battery low Disable underglow when battery below 10%, halve brightness when battery below 20% --- app/src/rgb_underglow.c | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/app/src/rgb_underglow.c b/app/src/rgb_underglow.c index 88faef09be1..5bc6a8277da 100644 --- a/app/src/rgb_underglow.c +++ b/app/src/rgb_underglow.c @@ -196,6 +196,7 @@ static void zmk_led_write_pixels(void) { static struct led_rgb led_buffer[STRIP_NUM_PIXELS]; int bat0 = zmk_battery_state_of_charge(); int blend = 0; + int reset_ext_power = 0; if (state.status_active) { blend = zmk_led_generate_status(); } @@ -205,6 +206,18 @@ static void zmk_led_write_pixels(void) { 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++) { @@ -227,10 +240,23 @@ 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; + 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) @@ -518,6 +544,12 @@ void zmk_rgb_set_ext_power(void) { c_power = 0; } int desired_state = state.on || state.status_active; + // force power off, when battery low (<10%) + if (state.on && !state.status_active) { + if (zmk_battery_state_of_charge() < 10) { + desired_state = false; + } + } if (desired_state && !c_power) { int rc = ext_power_enable(ext_power); if (rc != 0) { From f9c6ba44edd9c2c63ab86c2e21a26cadf75d9469 Mon Sep 17 00:00:00 2001 From: Donald Gordon Date: Sat, 21 Jan 2023 15:33:21 +1300 Subject: [PATCH 04/23] 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 | 9 +++++++++ app/src/ext_power_generic.c | 18 +++++++++++++----- 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/app/Kconfig b/app/Kconfig index 8155efd0ae5..18a0a711e7c 100644 --- a/app/Kconfig +++ b/app/Kconfig @@ -413,6 +413,15 @@ config ZMK_EXT_POWER bool "Enable support to control external power output" default y +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 diff --git a/app/src/ext_power_generic.c b/app/src/ext_power_generic.c index 2586f436852..9b179fcbb0a 100644 --- a/app/src/ext_power_generic.c +++ b/app/src/ext_power_generic.c @@ -145,18 +145,26 @@ static int ext_power_generic_init(const struct device *dev) { k_work_init_delayable(&ext_power_save_work, ext_power_save_state_work); - // Set default value (on) if settings isn't set + // Set default value if settings isn't set settings_load_subtree("ext_power"); if (!data->settings_init) { - data->status = true; + data->status = IS_ENABLED(CONFIG_ZMK_EXT_POWER_START); k_work_schedule(&ext_power_save_work, K_NO_WAIT); - ext_power_enable(dev); + if (data->status) { + ext_power_enable(dev); + } else { + ext_power_disable(dev); + } } #else - // Default to the ext_power being open when no settings - ext_power_enable(dev); + // If no settings, set ext_power on/off + if (IS_ENABLED(CONFIG_ZMK_EXT_POWER_START)) { + ext_power_enable(dev); + } else { + ext_power_disable(dev); + } #endif if (config->init_delay_ms) { From 2ff29d776cf6716138d6d33dc2029166829f40f7 Mon Sep 17 00:00:00 2001 From: Chris Andreae Date: Wed, 15 Feb 2023 14:10:21 +0900 Subject: [PATCH 05/23] Include the serial number in the Bluetooth device name --- app/Kconfig | 10 +++++++ app/boards/arm/glove80/glove80_lh_defconfig | 2 ++ app/boards/arm/glove80/usb_serial_number.c | 31 +++++++++++++------- app/src/ble.c | 32 +++++++++++++++++++++ 4 files changed, 64 insertions(+), 11 deletions(-) diff --git a/app/Kconfig b/app/Kconfig index 18a0a711e7c..f90b1d2ab57 100644 --- a/app/Kconfig +++ b/app/Kconfig @@ -218,6 +218,16 @@ config ZMK_BLE_CLEAR_BONDS_ON_START bool "Configuration that clears all bond information from the keyboard on startup." default n +config ZMK_BLE_DEVICE_NAME_APPEND_SN + bool "Append the device serial number to the Bluetooth device name" + default n + select BT_DEVICE_NAME_DYNAMIC + +config ZMK_BLE_DEVICE_NAME_SN_CHARS + int "Number of hexadecimal digits of serial number to append to the BT device name" + default 6 + depends on ZMK_BLE_DEVICE_NAME_APPEND_SN + # HID GATT notifications sent this way are *not* picked up by Linux, and possibly others. config BT_GATT_NOTIFY_MULTIPLE default n diff --git a/app/boards/arm/glove80/glove80_lh_defconfig b/app/boards/arm/glove80/glove80_lh_defconfig index a93f27cd8f2..b454e8be1bf 100644 --- a/app/boards/arm/glove80/glove80_lh_defconfig +++ b/app/boards/arm/glove80/glove80_lh_defconfig @@ -16,6 +16,8 @@ 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" diff --git a/app/boards/arm/glove80/usb_serial_number.c b/app/boards/arm/glove80/usb_serial_number.c index 44d7ee20363..6254cdc4d73 100644 --- a/app/boards/arm/glove80/usb_serial_number.c +++ b/app/boards/arm/glove80/usb_serial_number.c @@ -14,14 +14,15 @@ LOG_MODULE_DECLARE(usb_descriptor); int base16_encode(const uint8_t *data, int length, uint8_t *result, int bufSize); +void fill_serial_number(char *buf, int length); +/** + * nrf52840 hwinfo returns a 64-bit hardware id. Glove80 uses this as a + * serial number, encoded as base16 into the last 16 characters of the + * CONFIG_USB_DEVICE_SN template. If insufficient template space is + * available, instead return the static serial number string. + */ uint8_t *usb_update_sn_string_descriptor(void) { - /* - * nrf52840 hwinfo returns a 64-bit hardware id. Glove80 uses this as a - * serial number, encoded as base16 into the last 16 characters of the - * CONFIG_USB_DEVICE_SN template. If insufficient template space is - * available, instead return the static serial number string. - */ const uint8_t template_len = sizeof(CONFIG_USB_DEVICE_SN); const uint8_t sn_len = 16; @@ -33,17 +34,25 @@ uint8_t *usb_update_sn_string_descriptor(void) { static uint8_t serial[sizeof(CONFIG_USB_DEVICE_SN)]; strncpy(serial, CONFIG_USB_DEVICE_SN, template_len); + const uint8_t offset = template_len - sn_len - 1; + fill_serial_number(serial + offset, sn_len); + serial[template_len - 1] = '\0'; + + return serial; +} + +/** + * Writes the first `length` bytes of the device serial number into buf + */ +void fill_serial_number(char *buf, int length) { uint8_t hwid[8]; memset(hwid, 0, sizeof(hwid)); uint8_t hwlen = hwinfo_get_device_id(hwid, sizeof(hwid)); if (hwlen > 0) { - const uint8_t offset = template_len - sn_len - 1; - LOG_HEXDUMP_DBG(&hwid, sn_len, "Serial Number"); - base16_encode(hwid, hwlen, serial + offset, sn_len + 1); + LOG_HEXDUMP_DBG(&hwid, 16, "Serial Number"); + base16_encode(hwid, hwlen, buf, length); } - - return serial; } int base16_encode(const uint8_t *data, int length, uint8_t *result, int bufSize) { diff --git a/app/src/ble.c b/app/src/ble.c index f197e106a51..9d81af59298 100644 --- a/app/src/ble.c +++ b/app/src/ble.c @@ -62,9 +62,32 @@ enum advertising_type { static struct zmk_ble_profile profiles[ZMK_BLE_PROFILE_COUNT]; static uint8_t active_profile; +#if IS_ENABLED(CONFIG_ZMK_BLE_DEVICE_NAME_APPEND_SN) + +static char bt_device_name[sizeof(CONFIG_BT_DEVICE_NAME) + CONFIG_ZMK_BLE_DEVICE_NAME_SN_CHARS + 1]; + +void fill_serial_number(char *buf, int length); + +// configure the BT device name by appending a serial number prefix to +// CONFIG_BT_DEVICE_NAME +void init_bt_device_name(void) { + strncpy(bt_device_name, CONFIG_BT_DEVICE_NAME, sizeof(bt_device_name)); + bt_device_name[sizeof(CONFIG_BT_DEVICE_NAME) - 1] = ' '; + fill_serial_number(&bt_device_name[sizeof(CONFIG_BT_DEVICE_NAME)], + CONFIG_ZMK_BLE_DEVICE_NAME_SN_CHARS); + bt_device_name[sizeof(bt_device_name) - 1] = '\0'; +} + +#define DEVICE_NAME bt_device_name +#define DEVICE_NAME_LEN (sizeof(bt_device_name) - 1) + +#else + #define DEVICE_NAME CONFIG_BT_DEVICE_NAME #define DEVICE_NAME_LEN (sizeof(DEVICE_NAME) - 1) +#endif + BUILD_ASSERT(DEVICE_NAME_LEN <= 16, "ERROR: BLE device name is too long. Max length: 16"); static const struct bt_data zmk_ble_ad[] = { @@ -647,6 +670,10 @@ static void zmk_ble_ready(int err) { } static int zmk_ble_init(void) { +#if IS_ENABLED(CONFIG_ZMK_BLE_DEVICE_NAME_APPEND_SN) + init_bt_device_name(); +#endif + int err = bt_enable(NULL); if (err) { @@ -667,7 +694,12 @@ static int zmk_ble_init(void) { settings_load_subtree("ble"); settings_load_subtree("bt"); +#endif +#if IS_ENABLED(CONFIG_ZMK_BLE_DEVICE_NAME_APPEND_SN) + if (strcmp(bt_get_name(), bt_device_name) != 0) { + bt_set_name(bt_device_name); + } #endif #if IS_ENABLED(CONFIG_ZMK_BLE_CLEAR_BONDS_ON_START) From 3ae6e36d86567ede604beaf44070edd8a70821f2 Mon Sep 17 00:00:00 2001 From: Chris Andreae Date: Sat, 18 Sep 2021 20:38:30 +0900 Subject: [PATCH 06/23] Add build environment using Nix --- .gitattributes | 1 + README-NIX.md | 39 +++ default.nix | 51 ++++ nix/cmake-shell.nix | 17 ++ nix/generate-pin.rb | 34 +++ nix/manifest.json | 343 +++++++++++++++++++++++++ nix/pinned-nixpkgs.json | 4 + nix/pinned-nixpkgs.nix | 17 ++ nix/update-manifest/default.nix | 11 + nix/update-manifest/update-manifest.sh | 39 +++ nix/west-manifest.patch | 17 ++ nix/west-shell.nix | 50 ++++ nix/zephyr.nix | 52 ++++ nix/zmk.nix | 112 ++++++++ 14 files changed, 787 insertions(+) create mode 100644 README-NIX.md create mode 100644 default.nix create mode 100644 nix/cmake-shell.nix create mode 100755 nix/generate-pin.rb create mode 100644 nix/manifest.json create mode 100644 nix/pinned-nixpkgs.json create mode 100644 nix/pinned-nixpkgs.nix create mode 100644 nix/update-manifest/default.nix create mode 100755 nix/update-manifest/update-manifest.sh create mode 100644 nix/west-manifest.patch create mode 100644 nix/west-shell.nix create mode 100644 nix/zephyr.nix create mode 100644 nix/zmk.nix diff --git a/.gitattributes b/.gitattributes index 3d05d86d31a..af12493a041 100644 --- a/.gitattributes +++ b/.gitattributes @@ -4,3 +4,4 @@ # Docker on Windows. .bashrc text eol=lf *.sh text eol=lf +*.nix text eol=lf diff --git a/README-NIX.md b/README-NIX.md new file mode 100644 index 00000000000..a65c376a4a2 --- /dev/null +++ b/README-NIX.md @@ -0,0 +1,39 @@ +# Building Zephyrâ„¢ Mechanical Keyboard (ZMK) Firmware with Nix + +This extension is added by MoErgo for the Glove80 keyboard. + +Nix makes setup significantly easier. With this approach `west` is not needed. +You can however still choose to build using the standard Zephyr `west` toolchain +if you wish. + +# To build a target + +In ZMK root directory, + + nix-build -A *target* [-o *output_directory*] + +For example, + + nix-build -A glove80_left -o left + +The `output_directory` nix creates is a symlink. If you prefer not to rely on +symlink (perhaps because you are using WSL on Windows), you can make a copy of +the resulting `uf2` file using: + + cp -f $(nix-build -A *target* --no-out-link)/zmk.uf2 . + +# To build Glove80 + +In ZMK root directory, + + cp -f $(nix-build -A glove80_combined --no-out-link)/glove80.uf2 . + +# Adding new targets + +Edit default.nix and add an target based on zmk + +An example is: + + glove80_left = zmk.override { + board = "glove80_lh"; + }; diff --git a/default.nix b/default.nix new file mode 100644 index 00000000000..2a9f766e226 --- /dev/null +++ b/default.nix @@ -0,0 +1,51 @@ +{ pkgs ? (import ./nix/pinned-nixpkgs.nix {}) }: +let + inherit (pkgs) newScope; + inherit (pkgs.lib) makeScope; +in + +makeScope newScope (self: with self; { + west = pkgs.python3Packages.west.overrideAttrs(attrs: { + patches = (attrs.patches or []) ++ [./nix/west-manifest.patch]; + }); + + # To update the pinned Zephyr dependecies using west and update-manifest: + # nix shell -f . west -c west init -l app + # nix shell -f . west -c west update + # nix shell -f . update-manifest -c update-manifest > nix/manifest.json + # Note that any `group-filter` groups in west.yml need to be temporarily + # removed, as `west update-manifest` requires all dependencies to be fetched. + update-manifest = callPackage ./nix/update-manifest { inherit west; }; + + combine_uf2 = a: b: pkgs.runCommandNoCC "combined_${a.name}_${b.name}" {} + '' + mkdir -p $out + cat ${a}/zmk.uf2 ${b}/zmk.uf2 > $out/glove80.uf2 + ''; + + zephyr = callPackage ./nix/zephyr.nix { }; + + zmk = callPackage ./nix/zmk.nix { }; + + zmk_settings_reset = zmk.override { + shield = "settings_reset"; + }; + + glove80_left = zmk.override { + board = "glove80_lh"; + }; + + glove80_right = zmk.override { + board = "glove80_rh"; + }; + + glove80_combined = combine_uf2 glove80_left glove80_right; + + glove80_v0_left = zmk.override { + board = "glove80_v0_lh"; + }; + + glove80_v0_right = zmk.override { + board = "glove80_v0_rh"; + }; +}) diff --git a/nix/cmake-shell.nix b/nix/cmake-shell.nix new file mode 100644 index 00000000000..de68e6cf502 --- /dev/null +++ b/nix/cmake-shell.nix @@ -0,0 +1,17 @@ +{ pkgs ? (import ./pinned-nixpkgs.nix {}) }: + +let + zmkPkgs = (import ../default.nix { inherit pkgs; }); + inherit (zmkPkgs) zmk zephyr; + + zmkCmake = pkgs.writeShellScriptBin "zmk-cmake" '' + export PATH=${pkgs.lib.makeBinPath zmk.nativeBuildInputs}:$PATH + export CMAKE_PREFIX_PATH=${zephyr} + + cmake -G Ninja ${pkgs.lib.escapeShellArgs zmk.cmakeFlags} "-DUSER_CACHE_DIR=/tmp/.cache" "$@" + ''; +in +pkgs.stdenv.mkDerivation { + name = "zmk-cmake-shell"; + nativeBuildInputs = zmk.nativeBuildInputs ++ [zmkCmake]; +} diff --git a/nix/generate-pin.rb b/nix/generate-pin.rb new file mode 100755 index 00000000000..b6bf682dff2 --- /dev/null +++ b/nix/generate-pin.rb @@ -0,0 +1,34 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +require 'json' +require 'net/http' +require 'shellwords' + +def get_channel_url(channel) + uri = URI("https://channels.nixos.org/#{channel}") + res = Net::HTTP.get_response(uri) + raise 'Not a redirect' unless res.is_a?(Net::HTTPRedirection) + res['location'] +end + +def fetch_tarball(url) + hash = `nix-prefetch-url --unpack #{Shellwords.escape(url)}` + raise 'Prefetch failed' unless $?.success? + hash.chomp +end + +def generate_pin(channel) + channel_url = get_channel_url(channel) + nixexprs = "#{channel_url}/nixexprs.tar.xz" + hash = fetch_tarball(nixexprs) + { url: nixexprs, sha256: hash } +end + +def main + channel = ENV.fetch('CHANNEL', 'nixpkgs-unstable') + pin = generate_pin(channel) + puts JSON.pretty_generate(pin) +end + +main diff --git a/nix/manifest.json b/nix/manifest.json new file mode 100644 index 00000000000..4a942ef6589 --- /dev/null +++ b/nix/manifest.json @@ -0,0 +1,343 @@ +[ + { + "name": "zephyr", + "url": "https://github.com/petejohanson/zephyr", + "revision": "339570e2393b108bf8f9241018ecaf5615c25469", + "clone-depth": 1, + "west-commands": "scripts/west-commands.yml", + "sha256": "0m4hcp2017zg8c637siv941976lc377hp4z5kkijxznlb4c3xi8s" + }, + { + "name": "chre", + "url": "https://github.com/zephyrproject-rtos/chre", + "revision": "ef76d3456db07e4959df555047d6962279528c8d", + "path": "modules/lib/chre", + "groups": ["optional"], + "sha256": "00jnv58slzgp4srmsamd7x34yf0hj1c6agmws4csv7w28013bwfm" + }, + { + "name": "lz4", + "url": "https://github.com/zephyrproject-rtos/lz4", + "revision": "8e303c264fc21c2116dc612658003a22e933124d", + "path": "modules/lib/lz4", + "groups": ["optional"], + "sha256": "1kqs7gxg17gvws01rir8p6gmzp54y12s1898lflhsb418122v8nf" + }, + { + "name": "nanopb", + "url": "https://github.com/zephyrproject-rtos/nanopb", + "revision": "dc4deed54fd4c7e1935e3b6387eedf21bb45dc38", + "path": "modules/lib/nanopb", + "groups": ["optional"], + "sha256": "0kmzh65hyhnl88w9x0rcdypj4nl1brbxrnzhy3flgxmhr564y05s" + }, + { + "name": "psa-arch-tests", + "url": "https://github.com/zephyrproject-rtos/psa-arch-tests", + "revision": "f4fc2442b8e29e2a03d9899e46e5a3ea3df8c2c9", + "path": "modules/tee/tf-m/psa-arch-tests", + "groups": ["optional"], + "sha256": "015qan6qfqyfc1lwpqb29zk1wxx02b6ng5wc1nfpfvknvhiwifay" + }, + { + "name": "sof", + "url": "https://github.com/zephyrproject-rtos/sof", + "revision": "c0f20b69daa44e3563f970b366e49ccfcfa1b71c", + "path": "modules/audio/sof", + "groups": ["optional"], + "sha256": "1ijad7xp9a5j5fc0sdmdrf0l2k3fc4dnw8ka3glp4ay52dhy04zx" + }, + { + "name": "tf-m-tests", + "url": "https://github.com/zephyrproject-rtos/tf-m-tests", + "revision": "c99a86b295c4887520da9d8402566d7f225c974e", + "path": "modules/tee/tf-m/tf-m-tests", + "groups": ["optional"], + "sha256": "0cc4029i9qfzq4vply9l2yjigaf9rq1zc6a44b8dc779kx31qx36" + }, + { + "name": "tflite-micro", + "url": "https://github.com/zephyrproject-rtos/tflite-micro", + "revision": "1a34dcab41e7e0e667db72d6a40999c1ec9c510c", + "path": "optional/modules/lib/tflite-micro", + "groups": ["optional"], + "sha256": "194k1wh4fx49vymc3dhmmsg8wfq4jjhsp00rsrmax5vfhy7fjsi6" + }, + { + "name": "thrift", + "url": "https://github.com/zephyrproject-rtos/thrift", + "revision": "10023645a0e6cb7ce23fcd7fd3dbac9f18df6234", + "path": "optional/modules/lib/thrift", + "groups": ["optional"], + "sha256": "1bpi64lk7sdidjqsq7rspbqk9k7z8s26mgg17c8sv6cr4h007raq" + }, + { + "name": "zscilib", + "url": "https://github.com/zephyrproject-rtos/zscilib", + "revision": "ca070ddabdaf67175a2da901d0bd62e8899371c5", + "path": "modules/lib/zscilib", + "groups": ["optional"], + "sha256": "1ss33lfz2xzz8b3fcnibz109x09lj9ajw3na8lsmhvn7mhg6zh3v" + }, + { + "name": "acpica", + "url": "https://github.com/zephyrproject-rtos/acpica", + "revision": "0333c2af13179f9b33d495cf7cb9a509f751cbb1", + "path": "modules/lib/acpica", + "sha256": "04864is6iisjfasvlwgfsmgnlihdg43cfcc3hpqkzb446x2ix4iw" + }, + { + "name": "canopennode", + "url": "https://github.com/zephyrproject-rtos/canopennode", + "revision": "dec12fa3f0d790cafa8414a4c2930ea71ab72ffd", + "path": "modules/lib/canopennode", + "sha256": "0x6l56q5zdrz78iarfwxiwc05wwq4krg9xhx1z8kjarkwf6q9f85" + }, + { + "name": "cmsis", + "url": "https://github.com/zephyrproject-rtos/cmsis", + "revision": "5a00331455dd74e31e80efa383a489faea0590e3", + "path": "modules/hal/cmsis", + "groups": ["hal"], + "sha256": "00zqyin7bn6jwp7kq51dfs7sinwj5dhx4s981gdm171mmx7rx06n" + }, + { + "name": "cmsis-dsp", + "url": "https://github.com/zephyrproject-rtos/cmsis-dsp", + "revision": "ff7b5fd1ea5f094665c090c343ec44e74dc0b193", + "path": "modules/lib/cmsis-dsp", + "sha256": "0ycznb30fmh35sz3gsg3gzdh9shk8ad90k4849lmx21jzxr6jrai" + }, + { + "name": "cmsis-nn", + "url": "https://github.com/zephyrproject-rtos/cmsis-nn", + "revision": "0c8669d81381ccf3b1a01d699f3b68b50134a99f", + "path": "modules/lib/cmsis-nn", + "sha256": "1bnlyklzlhjrx8b66y6fy9im9wipx4shkq3mla5k7iw7kn3kqs7z" + }, + { + "name": "fatfs", + "url": "https://github.com/zephyrproject-rtos/fatfs", + "revision": "427159bf95ea49b7680facffaa29ad506b42709b", + "path": "modules/fs/fatfs", + "groups": ["fs"], + "sha256": "1pj6ik1bdyn07zsm0006n818mjzpbhhpsca4cw5imw66mhjy2pg6" + }, + { + "name": "hal_ambiq", + "url": "https://github.com/zephyrproject-rtos/hal_ambiq", + "revision": "0a7c99325aa73a1ef777501da91c2c6608661e56", + "path": "modules/hal/ambiq", + "groups": ["hal"], + "sha256": "1s1xv746p7c1ccyp2j1b36hrm9pp7dlpb8v4fclpd6wl3jcdjj74" + }, + { + "name": "hal_atmel", + "url": "https://github.com/zephyrproject-rtos/hal_atmel", + "revision": "5ab43007eda3f380c125f957f03638d2e8d1144d", + "path": "modules/hal/atmel", + "groups": ["hal"], + "sha256": "0csnr0npgfraf4g11dn4f77ywm2qd80y65a12kx68f7bfm8flf9j" + }, + { + "name": "hal_ethos_u", + "url": "https://github.com/zephyrproject-rtos/hal_ethos_u", + "revision": "90ada2ea5681b2a2722a10d2898eac34c2510791", + "path": "modules/hal/ethos_u", + "groups": ["hal"], + "sha256": "12nv46xhi8v6k36l7qgmlsp58vc1d0zw5wqizv30acj5si2bz3y3" + }, + { + "name": "hal_gigadevice", + "url": "https://github.com/zephyrproject-rtos/hal_gigadevice", + "revision": "2994b7dde8b0b0fa9b9c0ccb13474b6a486cddc3", + "path": "modules/hal/gigadevice", + "groups": ["hal"], + "sha256": "16h3l09ikbb4sqql7nlnfdxqvsgcdywrwckf83r4mjs9pgxiq1qa" + }, + { + "name": "hal_intel", + "url": "https://github.com/zephyrproject-rtos/hal_intel", + "revision": "b3b43d4e3da7ba483611bbbea7ef8af92c69df31", + "path": "modules/hal/intel", + "groups": ["hal"], + "sha256": "1m2d0vwa2iip80rfzip8av41lga2p8zm0gzxhd6x11b3lq05vkb2" + }, + { + "name": "hal_nordic", + "url": "https://github.com/zephyrproject-rtos/hal_nordic", + "revision": "d054a315eb888ba70e09e5f6decd4097b0276d1f", + "path": "modules/hal/nordic", + "groups": ["hal"], + "sha256": "0ypny416ylb2w5jg4bg55xvfg0yhqlbrakzvm0w23lnamg49kd6j" + }, + { + "name": "hal_nuvoton", + "url": "https://github.com/zephyrproject-rtos/hal_nuvoton", + "revision": "3e0a4c4d3328b2f72b164219add19d5308b53cb5", + "path": "modules/hal/nuvoton", + "groups": ["hal"], + "sha256": "1xc1cr4c0d0zzmbdrfb7xr8zzq0aifki1l50wcrn9dsi9c1ww12g" + }, + { + "name": "hal_quicklogic", + "url": "https://github.com/zephyrproject-rtos/hal_quicklogic", + "revision": "b3a66fe6d04d87fd1533a5c8de51d0599fcd08d0", + "path": "modules/hal/quicklogic", + "groups": ["hal"], + "sha256": "0hk1x72kibaw3xkspy9822vh28ax3bk11b80qn8l4dwrm0wx34sy" + }, + { + "name": "hal_renesas", + "url": "https://github.com/zephyrproject-rtos/hal_renesas", + "revision": "a6cf2af9140e014fbbc48d2b6deb802231dd369f", + "path": "modules/hal/renesas", + "groups": ["hal"], + "sha256": "000dmd1z72n9blw1pdjskkpz978k3pa6fzg60lhcaj9y24kl9y6z" + }, + { + "name": "hal_rpi_pico", + "url": "https://github.com/zephyrproject-rtos/hal_rpi_pico", + "revision": "fba7162cc7bee06d0149622bbcaac4e41062d368", + "path": "modules/hal/rpi_pico", + "groups": ["hal"], + "sha256": "01q3rf427vvfl0hn3af84hmb3g78dnd8n6saddy7mljha0z0dzda" + }, + { + "name": "hal_stm32", + "url": "https://github.com/zephyrproject-rtos/hal_stm32", + "revision": "89ef0a3383edebf661073073bcdf6e2836fe90ee", + "path": "modules/hal/stm32", + "groups": ["hal"], + "sha256": "0z7q5xg1rn9c3anjvi2kl0hgik3y3r25svwf97w1cjhjx1rhqmpv" + }, + { + "name": "hal_telink", + "url": "https://github.com/zephyrproject-rtos/hal_telink", + "revision": "38573af589173259801ae6c2b34b7d4c9e626746", + "path": "modules/hal/telink", + "groups": ["hal"], + "sha256": "16lzqnzwl2ij0jvbg9x1cgh54kv76dbmpcn4xhd5m4wph3yix78z" + }, + { + "name": "hal_wurthelektronik", + "url": "https://github.com/zephyrproject-rtos/hal_wurthelektronik", + "revision": "24ca9873c3d608fad1fea0431836bc8f144c132e", + "path": "modules/hal/wurthelektronik", + "groups": ["hal"], + "sha256": "0s2b3j40b7qd85np46n4vh0zjmwymnpxd8r42nhss6xznn11g2h8" + }, + { + "name": "libmetal", + "url": "https://github.com/zephyrproject-rtos/libmetal", + "revision": "b91611a6f47dd29fb24c46e5621e797557f80ec6", + "path": "modules/hal/libmetal", + "groups": ["hal"], + "sha256": "0pj0g6zaxiylpdiizf03jil4q2sq1z9px7cfxlv95ddnbv6rjjcz" + }, + { + "name": "liblc3", + "url": "https://github.com/zephyrproject-rtos/liblc3", + "revision": "448f3de31f49a838988a162ef1e23a89ddf2d2ed", + "path": "modules/lib/liblc3", + "sha256": "07r923k1y05sq1sl9740z33cz64pqm2n7x8rr2ws460fij64aixp" + }, + { + "name": "littlefs", + "url": "https://github.com/zephyrproject-rtos/littlefs", + "revision": "ca583fd297ceb48bced3c2548600dc615d67af24", + "path": "modules/fs/littlefs", + "groups": ["fs"], + "sha256": "10xpjrnp5n1j1xbay2qwmg2w314fw9pgzv3kz1mn3pgadhckfgdn" + }, + { + "name": "lvgl", + "url": "https://github.com/zephyrproject-rtos/lvgl", + "revision": "8a6a2d1d29d17d1e4bdc94c243c146a39d635fdd", + "path": "modules/lib/gui/lvgl", + "sha256": "0rsmlh358f4g2yidak916pxhkgckfrnck2a5hcsh9larsdnsnf24" + }, + { + "name": "mbedtls", + "url": "https://github.com/zephyrproject-rtos/mbedtls", + "revision": "c38dc78d9a8dcbe43b898cc1171ab33ba3e6fc26", + "path": "modules/crypto/mbedtls", + "groups": ["crypto"], + "sha256": "0661myy0wjz38nypbyfw51x10mzg57syb5c28irblgjm2w25wbi7" + }, + { + "name": "mipi-sys-t", + "url": "https://github.com/zephyrproject-rtos/mipi-sys-t", + "revision": "a819419603a2dfcb47f7f39092e1bc112e45d1ef", + "path": "modules/debug/mipi-sys-t", + "groups": ["debug"], + "sha256": "1ba0h4p3n0ldk5pjilj7z2i62b09vjq6ma668rqpr3w2hrg6941v" + }, + { + "name": "nrf_hw_models", + "url": "https://github.com/zephyrproject-rtos/nrf_hw_models", + "revision": "f4595802d32d103718bf50b3d390b7a450895843", + "path": "modules/bsim_hw_models/nrf_hw_models", + "sha256": "04p6nfyrv38vq0dhdz0g11zqlrqhw43lydyyr06vlrijm0zja8p5" + }, + { + "name": "open-amp", + "url": "https://github.com/zephyrproject-rtos/open-amp", + "revision": "42b7c577714b8f22ce82a901e19c1814af4609a8", + "path": "modules/lib/open-amp", + "sha256": "0ch1iv4c3v3zx1l2clm4mawyd83kvxl3b5sd6m7r2sn6wpr456w9" + }, + { + "name": "percepio", + "url": "https://github.com/zephyrproject-rtos/percepio", + "revision": "a3728efccc47dd372f40e6313589ca4c5cc7d5e9", + "path": "modules/debug/percepio", + "groups": ["debug"], + "sha256": "0fhz6jwsni7s79p5pwmynxn4yzrr40yd8m51y20d8jf1ka6dh4gw" + }, + { + "name": "picolibc", + "url": "https://github.com/zephyrproject-rtos/picolibc", + "revision": "d07c38ff051386f8e09a143ea0a6c1d6d66dd1d8", + "path": "modules/lib/picolibc", + "sha256": "1pb4piwyib1mmqjyycq8xanvx9aps6mz4w2ijgrn1fjgfh5f7zpq" + }, + { + "name": "segger", + "url": "https://github.com/zephyrproject-rtos/segger", + "revision": "9d0191285956cef43daf411edc2f1a7788346def", + "path": "modules/debug/segger", + "groups": ["debug"], + "sha256": "11wbzyd2n006ygh72ixhqcmgn1yrzk3kq3c0piqvrfgqmv7p8yzn" + }, + { + "name": "tinycrypt", + "url": "https://github.com/zephyrproject-rtos/tinycrypt", + "revision": "3e9a49d2672ec01435ffbf0d788db6d95ef28de0", + "path": "modules/crypto/tinycrypt", + "groups": ["crypto"], + "sha256": "19d2q9y23yzz9i383q3cldjl3k5mryx9762cab23zy3ijdnmj2z6" + }, + { + "name": "trusted-firmware-a", + "url": "https://github.com/zephyrproject-rtos/trusted-firmware-a", + "revision": "421dc050278287839f5c70019bd6aec617f2bbdb", + "path": "modules/tee/tf-a/trusted-firmware-a", + "groups": ["tee"], + "sha256": "029sha3dpagbvfnk5h6ad69pvs5ahwps3cxkzbfrdbq2xm8x3j2y" + }, + { + "name": "uoscore-uedhoc", + "url": "https://github.com/zephyrproject-rtos/uoscore-uedhoc", + "revision": "5fe2cb613bd7e4590bd1b00c2adf181ac0229379", + "path": "modules/lib/uoscore-uedhoc", + "sha256": "0z31lbibkvhw5dfps4l7049gggfczh5qqfjb4s3csnr447m81gdd" + }, + { + "name": "zcbor", + "url": "https://github.com/zephyrproject-rtos/zcbor", + "revision": "67fd8bb88d3136738661fa8bb5f9989103f4599e", + "path": "modules/lib/zcbor", + "sha256": "16138k7xlahf63dfvplm8c2m0kxs1g17gcx1fa31y4gcfbi3b0k7" + } +] diff --git a/nix/pinned-nixpkgs.json b/nix/pinned-nixpkgs.json new file mode 100644 index 00000000000..c5350cef317 --- /dev/null +++ b/nix/pinned-nixpkgs.json @@ -0,0 +1,4 @@ +{ + "url": "https://releases.nixos.org/nixpkgs/nixpkgs-24.05pre559581.aa9d4729cbc9/nixexprs.tar.xz", + "sha256": "04d8m0kh426kn2dvk5006as68al1dcv9wlizyclq69pas9xd1l3h" +} diff --git a/nix/pinned-nixpkgs.nix b/nix/pinned-nixpkgs.nix new file mode 100644 index 00000000000..c62cf91d1f9 --- /dev/null +++ b/nix/pinned-nixpkgs.nix @@ -0,0 +1,17 @@ +{ system ? builtins.currentSystem }: + +let + pin = builtins.fromJSON (builtins.readFile ./pinned-nixpkgs.json); + + nixpkgsSrc = builtins.fetchTarball { + inherit (pin) url sha256; + }; +in + +import nixpkgsSrc { + inherit system; + config = { + allowUnfree = true; + }; + overlays = []; # prevent impure overlays +} diff --git a/nix/update-manifest/default.nix b/nix/update-manifest/default.nix new file mode 100644 index 00000000000..eb858c16dbb --- /dev/null +++ b/nix/update-manifest/default.nix @@ -0,0 +1,11 @@ +{ runCommand, lib, makeWrapper, west, remarshal, nix-prefetch-git, jq, git }: + +runCommand "update-manifest" { + nativeBuildInputs = [ makeWrapper ]; +} '' + mkdir -p $out/bin $out/libexec + cp ${./update-manifest.sh} $out/libexec/update-manifest.sh + makeWrapper $out/libexec/update-manifest.sh $out/bin/update-manifest \ + --set PATH ${lib.makeBinPath [ west remarshal nix-prefetch-git jq git ]} + patchShebangs $out +'' diff --git a/nix/update-manifest/update-manifest.sh b/nix/update-manifest/update-manifest.sh new file mode 100755 index 00000000000..1f717a5a2b6 --- /dev/null +++ b/nix/update-manifest/update-manifest.sh @@ -0,0 +1,39 @@ +#!/usr/bin/env bash + +set -euo pipefail + +ignored_modules=(hal_espressif sof tflite-micro thrift bsim babblesim babblesim_base \ + babblesim_ext_2G4_libPhyComv1 babblesim_ext_2G4_channel_NtNcable \ + babblesim_ext_2G4_channel_multiatt babblesim_ext_2G4_modem_magic \ + babblesim_ext_2G4_modem_BLE_simple babblesim_ext_2G4_device_burst_interferer \ + babblesim_ext_2G4_device_WLAN_actmod babblesim_ext_2G4_phy_v1 \ + babblesim_ext_2G4_device_playback babblesim_ext_libCryptov1) + +prefetch_project() { + local p=$1 + local name + name="$(jq -r .name <<< "$p")" + + if [[ " ${ignored_modules[*]} " =~ " ${name} " ]]; then + echo "Skipping: $name" >&2 + return + fi + + echo "Prefetching: $name" >&2 + + sha256=$(nix-prefetch-git \ + --quiet \ + --fetch-submodules \ + --url "$(jq -r .url <<< "$p")" \ + --rev "$(jq -r .revision <<< "$p")" \ + | jq -r .sha256) + + jq --arg sha256 "$sha256" '. + $ARGS.named' <<< "$p" +} + + +west manifest --freeze | \ + yaml2json | \ + jq -c '.manifest.projects[]' | \ + while read -r p; do prefetch_project "$p"; done | \ + jq --slurp diff --git a/nix/west-manifest.patch b/nix/west-manifest.patch new file mode 100644 index 00000000000..f6f82b75735 --- /dev/null +++ b/nix/west-manifest.patch @@ -0,0 +1,17 @@ +diff --git a/src/west/manifest.py b/src/west/manifest.py +index e28fade..f3d3784 100644 +--- a/src/west/manifest.py ++++ b/src/west/manifest.py +@@ -1618,8 +1618,10 @@ class Manifest: + ''' + def pdict(p): + if not p.is_cloned(): +- raise RuntimeError(f'cannot freeze; project {p.name} ' +- 'is uncloned') ++ # For the purposes of exporting a frozen manifest for Nix, this ++ # is sufficient, as a package whose revision is not represented ++ # as a SHA will fail the prefetch. ++ return Project.as_dict(p) + try: + sha = p.sha(QUAL_MANIFEST_REV_BRANCH) + except subprocess.CalledProcessError as e: diff --git a/nix/west-shell.nix b/nix/west-shell.nix new file mode 100644 index 00000000000..a9be5f8db62 --- /dev/null +++ b/nix/west-shell.nix @@ -0,0 +1,50 @@ +{ pkgs ? (import ./pinned-nixpkgs.nix {}) }: + +let + # from zephyr/scripts/requirements-base.txt + pythonDependencies = ps: with ps; [ + pyelftools + pyyaml + packaging + progress + anytree + intelhex + west + ]; + + requiredStdenv = + if pkgs.stdenv.hostPlatform.isLinux + then pkgs.multiStdenv + else pkgs.stdenv; +in +with pkgs; +# requires multiStdenv to build 32-bit test binaries +requiredStdenv.mkDerivation { + name = "zmk-shell"; + + buildInputs = [ + # ZMK dependencies + gitFull + wget + autoconf + automake + bzip2 + ccache + dtc # devicetree compiler + dfu-util + gcc + libtool + ninja + cmake + xz + (python3.withPackages(pythonDependencies)) + + # ARM toolchain + gcc-arm-embedded + ]; + + ZEPHYR_TOOLCHAIN_VARIANT = "gnuarmemb"; + GNUARMEMB_TOOLCHAIN_PATH = gcc-arm-embedded; + + shellHook = "if [ ! -d \"zephyr\" ]; then west init -l app/ ; west update; west zephyr-export; fi; source zephyr/zephyr-env.sh"; +} diff --git a/nix/zephyr.nix b/nix/zephyr.nix new file mode 100644 index 00000000000..d7c1bd05624 --- /dev/null +++ b/nix/zephyr.nix @@ -0,0 +1,52 @@ +{ stdenvNoCC, lib, fetchgit, runCommand }: +let + manifestJSON = builtins.fromJSON (builtins.readFile ./manifest.json); + + mkModule = { name, revision, url, sha256, ... }: + stdenvNoCC.mkDerivation (finalAttrs: { + name = "zmk-module-${name}"; + + src = fetchgit { + inherit name url sha256; + rev = revision; + }; + + dontUnpack = true; + dontBuild = true; + + installPhase = '' + mkdir $out + ln -s ${finalAttrs.src} $out/${name} + ''; + + passthru = { + modulePath = "${finalAttrs.finalPackage}/${name}"; + }; + }); + + modules = lib.listToAttrs (lib.forEach manifestJSON ({ name, ... }@args: + lib.nameValuePair name (mkModule args))); +in + + +# Zephyr with no modules, from the frozen manifest. +# For now the modules are passed through as passthru +stdenvNoCC.mkDerivation { + name = "zephyr"; + src = modules.zephyr.src; + + dontBuild = true; + + # This awkward structure is required by + # COMMAND ${PYTHON_EXECUTABLE} ${ZEPHYR_BASE}/../tools/uf2/utils/uf2conv.py + installPhase = '' + mkdir -p $out/zephyr + mv * $out/zephyr + + # uf2 is gone, not sure what replaced it + ''; + + passthru = { + modules = removeAttrs modules ["zephyr"]; + }; +} diff --git a/nix/zmk.nix b/nix/zmk.nix new file mode 100644 index 00000000000..c467f60fa2c --- /dev/null +++ b/nix/zmk.nix @@ -0,0 +1,112 @@ +{ stdenvNoCC, lib, buildPackages +, cmake, ninja, dtc, gcc-arm-embedded +, zephyr +, board ? "glove80_lh" +, shield ? null +, keymap ? null +, kconfig ? null +}: + + +let + # from zephyr/scripts/requirements-base.txt + packageOverrides = pyself: pysuper: { + can = pysuper.can.overrideAttrs (_: { + # horribly flaky test suite full of assertions about timing. + # > assert 0.1 <= took < inc(0.3) + # E assert 0.31151700019836426 < 0.3 + # E + where 0.3 = inc(0.3) + doCheck = false; + doInstallCheck = false; + }); + + canopen = pysuper.can.overrideAttrs (_: { + # Also has timing sensitive tests + # task = self.network.send_periodic(0x123, [1, 2, 3], 0.01) + # time.sleep(0.1) + # > self.assertTrue(9 <= bus.queue.qsize() <= 11) + # E AssertionError: False is not true + doCheck = false; + doInstallCheck = false; + }); + }; + + python = (buildPackages.python3.override { inherit packageOverrides; }).withPackages (ps: with ps; [ + pyelftools + pyyaml + canopen + packaging + progress + anytree + intelhex + + # TODO: this was required but not in shell.nix + pykwalify + ]); + + requiredZephyrModules = [ + "cmsis" "hal_nordic" "tinycrypt" "picolibc" "lvgl" "picolibc" "segger" + ]; + + zephyrModuleDeps = + let modules = lib.attrVals requiredZephyrModules zephyr.modules; + in map (x: x.modulePath) modules; +in + +stdenvNoCC.mkDerivation { + name = "zmk_${board}"; + + sourceRoot = "source/app"; + + src = builtins.path { + name = "source"; + path = ./..; + filter = path: type: + let relPath = lib.removePrefix (toString ./.. + "/") (toString path); + in (lib.cleanSourceFilter path type) && ! ( + # Meta files + relPath == "nix" || lib.hasSuffix ".nix" path || + # Transient state + relPath == "build" || relPath == ".west" || + # Fetched by west + relPath == "modules" || relPath == "tools" || relPath == "zephyr" || + # Not part of ZMK + relPath == "lambda" || relPath == ".github" + ); + }; + + preConfigure = '' + cmakeFlagsArray+=("-DUSER_CACHE_DIR=$TEMPDIR/.cache") + ''; + + cmakeFlags = [ + # "-DZephyrBuildConfiguration_ROOT=${zephyr}/zephyr" + # TODO: is this required? if not, why not? + "-DZEPHYR_BASE=${zephyr}/zephyr" + "-DBOARD_ROOT=." + "-DBOARD=${board}" + "-DZEPHYR_TOOLCHAIN_VARIANT=gnuarmemb" + "-DGNUARMEMB_TOOLCHAIN_PATH=${gcc-arm-embedded}" + # TODO: maybe just use a cross environment for this gcc + "-DCMAKE_C_COMPILER=${gcc-arm-embedded}/bin/arm-none-eabi-gcc" + "-DCMAKE_CXX_COMPILER=${gcc-arm-embedded}/bin/arm-none-eabi-g++" + "-DCMAKE_AR=${gcc-arm-embedded}/bin/arm-none-eabi-ar" + "-DCMAKE_RANLIB=${gcc-arm-embedded}/bin/arm-none-eabi-ranlib" + "-DZEPHYR_MODULES=${lib.concatStringsSep ";" zephyrModuleDeps}" + ] ++ + (lib.optional (shield != null) "-DSHIELD=${shield}") ++ + (lib.optional (keymap != null) "-DKEYMAP_FILE=${keymap}") ++ + (lib.optional (kconfig != null) "-DEXTRA_CONF_FILE=${kconfig}"); + + nativeBuildInputs = [ cmake ninja python dtc gcc-arm-embedded ]; + buildInputs = [ zephyr ]; + + installPhase = '' + mkdir $out + cp zephyr/zmk.{uf2,hex,bin,elf} $out + cp zephyr/.config $out/zmk.kconfig + cp zephyr/zephyr.dts $out/zmk.dts + ''; + + passthru = { inherit zephyrModuleDeps; }; +} From 36d13c7dc65316af7d203647eb2eacbfd7fe7271 Mon Sep 17 00:00:00 2001 From: Chris Andreae Date: Sun, 18 Sep 2022 17:07:01 +0900 Subject: [PATCH 07/23] Github build action: build Glove80 combined firmware using Nix --- .github/workflows/build.yml | 13 +++---- .github/workflows/nix-build.yml | 62 +++++++++++++++++++++++++++++++++ 2 files changed, 69 insertions(+), 6 deletions(-) create mode 100644 .github/workflows/nix-build.yml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c2d1992dc43..17be98de748 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -14,7 +14,7 @@ on: jobs: build: - if: ${{ always() }} + if: ${{ false }} runs-on: ubuntu-latest container: image: docker.io/zmkfirmware/zmk-build-arm:3.5 @@ -131,7 +131,7 @@ jobs: throw new Error('Failed to build one or more configurations'); } compile-matrix: - if: ${{ always() }} + if: ${{ false && always() }} runs-on: ubuntu-latest needs: [core-coverage, board-changes, nightly] outputs: @@ -171,7 +171,7 @@ jobs: shieldArgs: JSON.stringify(shieldArgs), })); core-coverage: - if: ${{ needs.get-changed-files.outputs.core-changes == 'true' }} + if: ${{ false && needs.get-changed-files.outputs.core-changes == 'true' }} runs-on: ubuntu-latest needs: get-changed-files outputs: @@ -200,7 +200,7 @@ jobs: return [...include, ...coreCoverage.include]; board-changes: - if: ${{ needs.get-changed-files.outputs.board-changes == 'true' }} + if: ${{ false && needs.get-changed-files.outputs.board-changes == 'true' }} runs-on: ubuntu-latest needs: [get-grouped-hardware, get-changed-files] outputs: @@ -284,7 +284,7 @@ jobs: }); }))).flat(); nightly: - if: ${{ github.event_name == 'schedule' }} + if: ${{ false && github.event_name == 'schedule' }} runs-on: ubuntu-latest needs: get-grouped-hardware outputs: @@ -329,6 +329,7 @@ jobs: return [...includeOnboard, ...includeInterconnect]; get-grouped-hardware: + if: ${{ false }} runs-on: ubuntu-latest outputs: organized-metadata: ${{ steps.organize-metadata.outputs.result }} @@ -406,7 +407,7 @@ jobs: return JSON.stringify(grouped).replace(/\\/g,"\\\\").replace(/`/g,"\\`"); result-encoding: string get-changed-files: - if: ${{ github.event_name != 'schedule' }} + if: ${{ false && github.event_name != 'schedule' }} runs-on: ubuntu-latest outputs: changed-files: ${{ steps.changed-files.outputs.all_changed_files }} diff --git a/.github/workflows/nix-build.yml b/.github/workflows/nix-build.yml new file mode 100644 index 00000000000..d864503167e --- /dev/null +++ b/.github/workflows/nix-build.yml @@ -0,0 +1,62 @@ +name: Build + +on: + push: + paths: + - ".github/workflows/nix-build.yml" + - "default.nix" + - "app/**" + - "nix/**" + branches: + - "**" + tags: + - "**" + pull_request: + paths: + - ".github/workflows/nix-build.yml" + - "default.nix" + - "app/**" + - "nix/**" + +jobs: + build: + name: Build Glove80 Firmware + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2.4.0 + - uses: cachix/install-nix-action@v20 + with: + nix_path: nixpkgs=channel:nixos-22.05 + - uses: cachix/cachix-action@v12 + with: + name: moergo-glove80-zmk-dev + authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}" + skipPush: "${{ github.repository != 'moergo-sc/zmk' }}" + - name: Build Glove80 combined firmware + run: nix-build -A glove80_combined -o combined + - name: Copy result out of nix store + run: cp combined/glove80.uf2 glove80.uf2 + - name: Upload result + uses: actions/upload-artifact@v3 + with: + name: glove80.uf2 + path: glove80.uf2 + release: + name: Create Release for Tag + if: >- + github.repository == 'moergo-sc/zmk' + && github.event_name == 'push' + && contains(github.ref, 'refs/tags/v') + needs: build + runs-on: ubuntu-latest + steps: + - name: Download compiled firmware artifact + uses: actions/download-artifact@v3 + with: + name: glove80.uf2 + - name: Create Release for Tag + uses: ncipollo/release-action@v1 + with: + artifacts: "glove80.uf2" + artifactErrorsFailBuild: true + generateReleaseNotes: true From ab52ae918e6bc488591de720aa07a5f15737c7e8 Mon Sep 17 00:00:00 2001 From: Chris Andreae Date: Mon, 11 Oct 2021 16:49:33 +0900 Subject: [PATCH 08/23] Docker container and lambda function for performing firmware builds Provides an entry point that builds and returns a combined LH + RH keyboard firmware when provided a keymap via a POST body. Wraps compilation with ccache, and includes a pre-warmed cache of the build in /tmp/ccache. To maximize chance of a direct cache hit, changes the lambda driver to always build in /tmp/build. some back of the envelope measurements (2012 xeon e3-1230v2, nixos) clean build, no cache -> 21.308 clean build, cache -> 7.145 modified keymap, clean build, cache -> 12.127 --- .github/workflows/build-container.yml | 110 +++++++++++++ .github/workflows/cleanup-container.yml | 43 +++++ .github/workflows/nix-build.yml | 2 +- lambda/Gemfile | 3 + lambda/Gemfile.lock | 13 ++ lambda/api_version.txt | 1 + lambda/app.rb | 68 ++++++++ lambda/compiler.rb | 131 +++++++++++++++ lambda/default.nix | 26 +++ lambda/gemset.nix | 12 ++ lambda/shell.nix | 9 + nix/ccache.nix | 43 +++++ release.nix | 208 ++++++++++++++++++++++++ 13 files changed, 668 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/build-container.yml create mode 100644 .github/workflows/cleanup-container.yml create mode 100644 lambda/Gemfile create mode 100644 lambda/Gemfile.lock create mode 100644 lambda/api_version.txt create mode 100644 lambda/app.rb create mode 100644 lambda/compiler.rb create mode 100644 lambda/default.nix create mode 100644 lambda/gemset.nix create mode 100644 lambda/shell.nix create mode 100644 nix/ccache.nix create mode 100644 release.nix diff --git a/.github/workflows/build-container.yml b/.github/workflows/build-container.yml new file mode 100644 index 00000000000..e5c3224865d --- /dev/null +++ b/.github/workflows/build-container.yml @@ -0,0 +1,110 @@ +name: Build Compiler Service Container + +on: + push: + tags: + - "*" + pull_request_target: + branches: + - main + +jobs: + build: + # This job must never be run on a PR from outside the same repository + if: github.repository == 'moergo-sc/zmk' && (github.event.pull_request == null || github.event.pull_request.head.repo.full_name == github.repository) + runs-on: ubuntu-latest + # These permissions are needed to interact with GitHub's OIDC Token endpoint. + permissions: + id-token: write + contents: read + env: + ECR_REPOSITORY: zmk-builder-lambda + VERSIONS_BUCKET: glove80firmwarepipelines-compilerversionsbucket44-zubaquiyjdam + UPDATE_COMPILER_VERSIONS_FUNCTION: arn:aws:lambda:us-east-1:431227615537:function:Glove80FirmwarePipelineSt-UpdateCompilerVersions2A-CNxPOHb4VSuV + REVISION_TAG: ${{ github.event.pull_request && github.event.pull_request.head.sha || github.sha }} + PR_NUMBER: ${{ github.event.number }} + steps: + - uses: actions/checkout@v2.4.0 + with: + repository: moergo-sc/zmk + ref: ${{ github.event.pull_request && github.event.pull_request.head.sha || github.sha }} + fetch-depth: 0 + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@v1 + with: + role-to-assume: arn:aws:iam::431227615537:role/GithubCompilerLambdaBuilder + aws-region: us-east-1 + - name: Extract container name from branch name + shell: bash + run: | + if [ "$GITHUB_HEAD_REF" ]; then + branch_ref="$GITHUB_HEAD_REF" + type="pr" + tag="pr${PR_NUMBER}.${GITHUB_HEAD_REF}" + elif [[ "$GITHUB_REF" == refs/tags/* ]]; then + branch_ref="$GITHUB_REF" + type="tag" + tag="${GITHUB_REF#refs/tags/}" + else + echo "Not a pull request or release tag" >&2 + exit 1 + fi + # Replace / with . in container tag names + tag="${tag//\//.}" + echo "VERSION_BRANCH=${branch_ref}" >> $GITHUB_ENV + echo "VERSION_TYPE=${type}" >> $GITHUB_ENV + echo "VERSION_NAME=${tag}" >> $GITHUB_ENV + id: extract_name + - name: Login to Amazon ECR + id: login-ecr + uses: aws-actions/amazon-ecr-login@v1 + - uses: cachix/install-nix-action@v20 + with: + nix_path: nixpkgs=channel:nixos-22.05 + - uses: cachix/cachix-action@v12 + with: + name: moergo-glove80-zmk-dev + authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}" + - name: Build lambda image + run: nix-build release.nix --arg revision "\"${REVISION_TAG}\"" -A lambdaImage -o lambdaImage + - name: Import OCI image into docker-daemon + env: + REGISTRY: ${{ steps.login-ecr.outputs.registry }} + run: skopeo --insecure-policy copy oci:lambdaImage docker-daemon:$REGISTRY/$ECR_REPOSITORY:$REVISION_TAG + - name: Push container image to Amazon ECR + env: + REGISTRY: ${{ steps.login-ecr.outputs.registry }} + run: docker push $REGISTRY/$ECR_REPOSITORY:$REVISION_TAG + - name: Create JSON metadata to represent the built container + env: + REGISTRY: ${{ steps.login-ecr.outputs.registry }} + shell: bash + run: | + digest="$(docker inspect --format='{{index .RepoDigests 0}}' $REGISTRY/$ECR_REPOSITORY:$REVISION_TAG)" + digest="${digest##*@}" + api_version="$(cat lambda/api_version.txt)" + timestamp="$(date -u +"%Y%m%d.%H%M%S")" + + if [ "$VERSION_TYPE" = "pr" ]; then + release_name="$VERSION_NAME.$timestamp" + else + release_name="$VERSION_NAME" + fi + + jq -n '$ARGS.named' \ + --arg name "$release_name" \ + --arg version_name "$VERSION_NAME" \ + --arg revision "$REVISION_TAG" \ + --arg release_time "$timestamp" \ + --arg branch "$VERSION_BRANCH" \ + --arg digest "$digest" \ + --arg api_version "$api_version" \ + > "/tmp/$VERSION_NAME.json" + - name: Upload image metadata file to versions bucket + run: aws s3 cp "/tmp/$VERSION_NAME.json" "s3://$VERSIONS_BUCKET/images/$VERSION_NAME.json" + - name: Notify the build pipeline that the compile containers have updated + run: >- + aws lambda invoke --function-name $UPDATE_COMPILER_VERSIONS_FUNCTION + --invocation-type Event + --cli-binary-format raw-in-base64-out + /dev/null diff --git a/.github/workflows/cleanup-container.yml b/.github/workflows/cleanup-container.yml new file mode 100644 index 00000000000..0af80562cca --- /dev/null +++ b/.github/workflows/cleanup-container.yml @@ -0,0 +1,43 @@ +name: Clean up PR Compiler Service Container + +on: + pull_request: + types: [closed] + branches: + - main + +jobs: + build: + if: github.repository == 'moergo-sc/zmk' + runs-on: ubuntu-latest + # These permissions are needed to interact with GitHub's OIDC Token endpoint. + permissions: + id-token: write + contents: read + env: + ECR_REPOSITORY: zmk-builder-lambda + VERSIONS_BUCKET: glove80firmwarepipelines-compilerversionsbucket44-zubaquiyjdam + UPDATE_COMPILER_VERSIONS_FUNCTION: arn:aws:lambda:us-east-1:431227615537:function:Glove80FirmwarePipelineSt-UpdateCompilerVersions2A-CNxPOHb4VSuV + PR_NUMBER: ${{ github.event.number }} + steps: + - name: Extract image tag name + shell: bash + run: | + tag="pr${PR_NUMBER}.${GITHUB_HEAD_REF}" + # Replace / with . in container tag names + tag="${tag//\//.}" + echo "VERSION_NAME=${tag}" >> $GITHUB_ENV + id: extract_name + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@v1 + with: + role-to-assume: arn:aws:iam::431227615537:role/GithubCompilerLambdaBuilder + aws-region: us-east-1 + - name: Delete the image metadata file from the versions s3 bucket + run: aws s3 rm s3://$VERSIONS_BUCKET/images/$VERSION_NAME.json + - name: Notify the build pipeline that the compile containers have updated + run: >- + aws lambda invoke --function-name $UPDATE_COMPILER_VERSIONS_FUNCTION + --invocation-type Event + --cli-binary-format raw-in-base64-out + /dev/null diff --git a/.github/workflows/nix-build.yml b/.github/workflows/nix-build.yml index d864503167e..faaeabc220e 100644 --- a/.github/workflows/nix-build.yml +++ b/.github/workflows/nix-build.yml @@ -1,4 +1,4 @@ -name: Build +name: Build Glove80 Firmware on: push: diff --git a/lambda/Gemfile b/lambda/Gemfile new file mode 100644 index 00000000000..6b6bbf753ec --- /dev/null +++ b/lambda/Gemfile @@ -0,0 +1,3 @@ +source 'https://rubygems.org' +gem 'aws_lambda_ric' + diff --git a/lambda/Gemfile.lock b/lambda/Gemfile.lock new file mode 100644 index 00000000000..8b6c1f95c58 --- /dev/null +++ b/lambda/Gemfile.lock @@ -0,0 +1,13 @@ +GEM + remote: https://rubygems.org/ + specs: + aws_lambda_ric (2.0.0) + +PLATFORMS + ruby + +DEPENDENCIES + aws_lambda_ric + +BUNDLED WITH + 2.1.4 diff --git a/lambda/api_version.txt b/lambda/api_version.txt new file mode 100644 index 00000000000..0cfbf08886f --- /dev/null +++ b/lambda/api_version.txt @@ -0,0 +1 @@ +2 diff --git a/lambda/app.rb b/lambda/app.rb new file mode 100644 index 00000000000..8c3eb554cca --- /dev/null +++ b/lambda/app.rb @@ -0,0 +1,68 @@ +# frozen_string_literal: true + +require 'stringio' +require 'digest' +require 'json' +require './compiler' + +module LambdaFunction + # Handle a non-HTTP compile request, returning a JSON body of either the + # compiled result or an error. + class Handler + REVISION = ENV.fetch('REVISION', 'unknown') + + def self.process(event:, context:) + return { type: 'keep_alive' } if event.has_key?('keep_alive') + + parse_base64_param = ->(param, required: true) do + if event.include?(param) + Base64.strict_decode64(event.fetch(param)) + elsif required + return error(status: 400, message: "Missing required argument: #{param}") + end + rescue ArgumentError + return error(status: 400, message: "Invalid Base64 in #{param} input") + end + + keymap_data = parse_base64_param.('keymap') + kconfig_data = parse_base64_param.('kconfig', required: false) + + # Including kconfig settings that affect the RHS require building both + # firmware images, doubling compile time. Clients should omit rhs_kconfig + # where possible. + rhs_kconfig_data = parse_base64_param.('rhs_kconfig', required: false) + + result, log = + begin + log_compile(keymap_data, kconfig_data, rhs_kconfig_data) + + Compiler.new.compile(keymap_data, kconfig_data, rhs_kconfig_data) + rescue Compiler::CompileError => e + return error(status: e.status, message: e.message, detail: e.log) + end + + result = Base64.strict_encode64(result) + + { type: 'result', result: result, log: log, revision: REVISION } + rescue StandardError => e + error(status: 500, message: "Unexpected error: #{e.class}", detail: [e.message], exception: e) + end + + def self.log_compile(keymap_data, kconfig_data, rhs_kconfig_data) + keymap = Digest::SHA1.base64digest(keymap_data) + kconfig = kconfig_data ? Digest::SHA1.base64digest(kconfig_data) : 'nil' + rhs_kconfig = rhs_kconfig_data ? Digest::SHA1.base64digest(rhs_kconfig_data) : 'nil' + puts("Compiling with keymap: #{keymap}; kconfig: #{kconfig}; rhs_kconfig: #{rhs_kconfig}") + end + + def self.error(status:, message:, detail: nil, exception: nil) + reported_error = { type: 'error', status:, message:, detail:, revision: REVISION } + + exception_detail = { class: exception.class, backtrace: exception.backtrace } if exception + logged_error = reported_error.merge(exception: exception_detail) + puts(JSON.dump(logged_error)) + + reported_error + end + end +end diff --git a/lambda/compiler.rb b/lambda/compiler.rb new file mode 100644 index 00000000000..58ab0eaef8d --- /dev/null +++ b/lambda/compiler.rb @@ -0,0 +1,131 @@ +# frozen_string_literal: true + +require 'tmpdir' +require 'base64' +require 'json' +require 'open3' +require 'yaml' + +class Compiler + class CompileError < RuntimeError + attr_reader :status, :log + + def initialize(message, status: 400, log:) + super(message) + @status = status + @log = log + end + end + + def compile(keymap_data, lhs_kconfig_data, rhs_kconfig_data) + if rhs_kconfig_data && !rhs_kconfig_data.empty? + lhs_result, lhs_output = compile_board('glove80_lh', keymap_data:, kconfig_data: lhs_kconfig_data, include_static_rhs: false) + rhs_result, rhs_output = compile_board('glove80_rh', keymap_data: nil, kconfig_data: rhs_kconfig_data, include_static_rhs: false) + [ + lhs_result.concat(rhs_result), + ["LHS Output:", *lhs_output, "RHS Output:", *rhs_output], + ] + else + compile_board('glove80_lh', keymap_data:, kconfig_data: lhs_kconfig_data, include_static_rhs: true) + end + end + + def compile_board(board, keymap_data:, kconfig_data:, include_static_rhs: false) + in_build_dir do + compile_command = ['compileZmk', '-b', board] + + if keymap_data + dts_parse_errors = validate_devicetree!(keymap_data) + File.open('build.keymap', 'w') { |io| io.write(keymap_data) } + compile_command << '-k' << './build.keymap' + end + + if kconfig_data + File.open('build.conf', 'w') { |io| io.write(kconfig_data) } + compile_command << '-c' << './build.conf' + end + + if include_static_rhs + # Concatenate the pre-compiled glove80_rh image to the resulting uf2 + compile_command << '-m' + end + + compile_output = nil + + IO.popen(compile_command, 'rb', err: [:child, :out]) do |io| + compile_output = io.read + end + + compile_output = compile_output.split("\n") + + unless $?.success? + status = $?.exitstatus + raise CompileError.new("Compile failed with exit status #{status}", log: compile_output) + end + + unless File.exist?('zmk.uf2') + raise CompileError.new('Compile failed to produce result binary', status: 500, log: compile_output) + end + + if dts_parse_errors + # DTS validation failed to parse the DTS, yet the Zephyr build + # nonetheless succeeded. We can't allow returning the result, since we + # were unable to check it for unsafe dts sections. + raise CompileError.new('Syntax error validating device-tree input', log: dts_parse_errors) + end + + result = File.read('zmk.uf2') + + [result, compile_output] + end + end + + PERMITTED_DTS_SECTIONS = %w[ + behaviors macros combos conditional_layers keymap underglow-indicators + ].freeze + + def validate_devicetree!(dtsi) + dts = "/dts-v1/;\n" + dtsi + + stdout, stderr, status = + Open3.capture3({}, 'dts2yml', unsetenv_others: true, stdin_data: dts) + + unless status.success? + # The error output from dtc is much harder to understand than Zephyr's + # errors, and the line numbers don't match up due to preprocessing. Rather + # than raising these now, return the error output in order that it's only + # used in the case that the Zephyr build doesn't itself error. + return stderr.split("\n") + end + + data = + begin + YAML.safe_load(stdout) + rescue Psych::Exception => e + raise CompileError.new('Error parsing translated device-tree', status: 500, log: [e.message]) + end + + sections = data.flat_map(&:keys) + invalid_sections = sections - PERMITTED_DTS_SECTIONS + + unless invalid_sections.empty? + raise CompileError.new( + "Device-tree included the non-permitted root sections: #{invalid_sections.inspect}", log: []) + end + + nil + end + + # Lambda is single-process per container, and we get substantial speedups + # from ccache by always building in the same path + BUILD_DIR = '/tmp/build' + + def in_build_dir + FileUtils.remove_entry(BUILD_DIR, true) + Dir.mkdir(BUILD_DIR) + Dir.chdir(BUILD_DIR) + yield + ensure + FileUtils.remove_entry(BUILD_DIR, true) rescue nil + end +end diff --git a/lambda/default.nix b/lambda/default.nix new file mode 100644 index 00000000000..6a34b0d1122 --- /dev/null +++ b/lambda/default.nix @@ -0,0 +1,26 @@ +{ pkgs ? import {} }: + +with pkgs; + +let + bundleEnv = bundlerEnv { + name = "lambda-bundler-env"; + ruby = ruby_3_1; + gemfile = ./Gemfile; + lockfile = ./Gemfile.lock; + gemset = ./gemset.nix; + }; + + source = stdenv.mkDerivation { + name = "lambda-builder"; + version = "0.0.1"; + src = ./.; + installPhase = '' + cp -r ./ $out + ''; + }; + +in +{ + inherit bundleEnv source; +} diff --git a/lambda/gemset.nix b/lambda/gemset.nix new file mode 100644 index 00000000000..6b2fd1a0207 --- /dev/null +++ b/lambda/gemset.nix @@ -0,0 +1,12 @@ +{ + aws_lambda_ric = { + groups = ["default"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "19c4xlgnhgwf3n3z57z16nmr76jd2vihhshknm5zqip2g00awhi1"; + type = "gem"; + }; + version = "2.0.0"; + }; +} diff --git a/lambda/shell.nix b/lambda/shell.nix new file mode 100644 index 00000000000..2f1eca8bb75 --- /dev/null +++ b/lambda/shell.nix @@ -0,0 +1,9 @@ +{ pkgs ? (import {})}: + +let + lambda = import ./default.nix { inherit pkgs; }; +in +pkgs.stdenv.mkDerivation { + name = "lambda-shell"; + buildInputs = [lambda.bundleEnv.wrappedRuby]; +} diff --git a/nix/ccache.nix b/nix/ccache.nix new file mode 100644 index 00000000000..030153140e2 --- /dev/null +++ b/nix/ccache.nix @@ -0,0 +1,43 @@ +{ stdenv, lib, makeWrapper, ccache +, unwrappedCC ? stdenv.cc.cc, extraConfig ? "" }: + +# copied from ccache in nixpkgs, modified to glob over prefixes. Also doesn't +# pass lib. Why was it passing lib? +stdenv.mkDerivation { + name = "ccache-links"; + passthru = { + isClang = unwrappedCC.isClang or false; + isGNU = unwrappedCC.isGNU or false; + }; + nativeBuildInputs = [ makeWrapper ]; + buildCommand = '' + mkdir -p $out/bin + + wrap() { + local cname="$(basename $1)" + if [ -x "${unwrappedCC}/bin/$cname" ]; then + echo "Wrapping $1" + makeWrapper ${ccache}/bin/ccache $out/bin/$cname \ + --run ${lib.escapeShellArg extraConfig} \ + --add-flags ${unwrappedCC}/bin/$cname + fi + } + + wrapAll() { + for prog in "$@"; do + wrap "$prog" + done + } + + wrapAll ${unwrappedCC}/bin/{*cc,*c++,*gcc,*g++,*clang,*clang++} + + for executable in $(ls ${unwrappedCC}/bin); do + if [ ! -x "$out/bin/$executable" ]; then + ln -s ${unwrappedCC}/bin/$executable $out/bin/$executable + fi + done + for file in $(ls ${unwrappedCC} | grep -vw bin); do + ln -s ${unwrappedCC}/$file $out/$file + done + ''; +} diff --git a/release.nix b/release.nix new file mode 100644 index 00000000000..f4137e4e5cb --- /dev/null +++ b/release.nix @@ -0,0 +1,208 @@ +{ pkgs ? (import ./nix/pinned-nixpkgs.nix {}), revision ? "HEAD" }: + +let + lib = pkgs.lib; + zmkPkgs = (import ./default.nix { inherit pkgs; }); + lambda = (import ./lambda { inherit pkgs; }); + ccacheWrapper = pkgs.callPackage ./nix/ccache.nix {}; + + nix-utils = pkgs.fetchFromGitHub { + owner = "iknow"; + repo = "nix-utils"; + rev = "c13c7a23836c8705452f051d19fc4dff05533b53"; + sha256 = "0ax7hld5jf132ksdasp80z34dlv75ir0ringzjs15mimrkw8zcac"; + }; + + ociTools = pkgs.callPackage "${nix-utils}/oci" {}; + + inherit (zmkPkgs) zmk zephyr; + + accounts = { + users.deploy = { + uid = 999; + group = "deploy"; + home = "/home/deploy"; + shell = "/bin/sh"; + }; + groups.deploy.gid = 999; + }; + + baseLayer = { + name = "base-layer"; + path = [ pkgs.busybox ]; + entries = ociTools.makeFilesystem { + inherit accounts; + tmp = true; + usrBinEnv = "${pkgs.busybox}/bin/env"; + binSh = "${pkgs.busybox}/bin/sh"; + }; + }; + + depsLayer = { + name = "deps-layer"; + path = [ pkgs.ccache ]; + includes = zmk.buildInputs ++ zmk.nativeBuildInputs ++ zmk.zephyrModuleDeps; + }; + + dts2yml = pkgs.writeShellScriptBin "dts2yml" '' + set -eo pipefail + + ${pkgs.gcc-arm-embedded}/bin/arm-none-eabi-cpp -P -D__DTS__ -E -nostdinc \ + -I "${zmk.src}/app/dts" -I "${zmk.src}/app/include" \ + -I "${zephyr}/zephyr/dts" -I "${zephyr}/zephyr/dts/common" -I "${zephyr}/zephyr/dts/arm" \ + -I "${zephyr}/zephyr/include" -I "${zephyr}/zephyr/include/zephyr"\ + -undef -x assembler-with-cpp - |\ + ${pkgs.dtc}/bin/dtc -I dts -O yaml + ''; + + zmkCompileScript = let + zmk' = zmk.override { + gcc-arm-embedded = ccacheWrapper.override { + unwrappedCC = pkgs.gcc-arm-embedded; + }; + }; + zmk_glove80_rh = zmk.override { board = "glove80_rh"; }; + realpath_coreutils = if pkgs.stdenv.isDarwin then pkgs.coreutils else pkgs.busybox; + in pkgs.writeShellScriptBin "compileZmk" '' + set -eo pipefail + + function usage() { + echo "Usage: compileZmk [-m] [-k keymap_file] [-c kconfig_file] [-b board]" + } + + function checkPath() { + if [ -z "$1" ]; then + return 0 + elif [ ! -f "$1" ]; then + echo "Error: Missing $2 file" >&2 + usage >&2 + exit 1 + fi + + ${realpath_coreutils}/bin/realpath "$1" + } + + keymap="${zmk.src}/app/boards/arm/glove80/glove80.keymap" + kconfig="" + board="glove80_lh" + merge_rhs="" + + while getopts "hk:c:d:b:m" opt; do + case "$opt" in + h|\?) + usage >&2 + exit 1 + ;; + k) + keymap="$OPTARG" + ;; + c) + kconfig="$OPTARG" + ;; + b) + board="$OPTARG" + ;; + m) + merge_rhs=t + ;; + esac + done + + if [ "$board" = "glove80_rh" -a -n "$merge_rhs" ]; then + echo "Cannot merge static RHS with built RHS" >&2 + exit 2 + fi + + keymap="$(checkPath "$keymap" keymap)" + kconfig="$(checkPath "$kconfig" Kconfig)" + + export PATH=${lib.makeBinPath (with pkgs; zmk'.nativeBuildInputs ++ [ ccache ])}:$PATH + export CMAKE_PREFIX_PATH=${zephyr} + + export CCACHE_BASEDIR=$PWD + export CCACHE_NOHASHDIR=t + export CCACHE_COMPILERCHECK=none + + if [ -n "$DEBUG" ]; then ccache -z; fi + + cmake -G Ninja -S ${zmk'.src}/app ${lib.escapeShellArgs zmk'.cmakeFlags} "-DUSER_CACHE_DIR=/tmp/.cache" "-DKEYMAP_FILE=$keymap" "-DBOARD=$board" "-DEXTRA_CONF_FILE=$kconfig" "-DBUILD_VERSION=${revision}" + + ninja + + if [ -n "$DEBUG" ]; then ccache -s; fi + + if [ -n "$merge_rhs" ]; then + cat zephyr/zmk.uf2 ${zmk_glove80_rh}/zmk.uf2 > zmk.uf2 + else + mv zephyr/zmk.uf2 zmk.uf2 + fi + ''; + + ccacheCache = pkgs.runCommandNoCC "ccache-cache" { + nativeBuildInputs = [ zmkCompileScript ]; + } '' + export CCACHE_DIR=$out + + mkdir /tmp/build + cd /tmp/build + + compileZmk -b glove80_lh -k ${zmk.src}/app/boards/arm/glove80/glove80.keymap + + rm -fr /tmp/build + mkdir /tmp/build + cd /tmp/build + + compileZmk -b glove80_rh -k ${zmk.src}/app/boards/arm/glove80/glove80.keymap + ''; + + entrypoint = pkgs.writeShellScriptBin "entrypoint" '' + set -euo pipefail + + if [ ! -d "$CCACHE_DIR" ]; then + cp -r ${ccacheCache} "$CCACHE_DIR" + chmod -R u=rwX,go=u-w "$CCACHE_DIR" + fi + + if [ ! -d /tmp/build ]; then + mkdir /tmp/build + fi + + exec "$@" + ''; + + startLambda = pkgs.writeShellScriptBin "startLambda" '' + set -euo pipefail + export PATH=${lib.makeBinPath [ zmkCompileScript dts2yml ]}:$PATH + cd ${lambda.source} + ${lambda.bundleEnv}/bin/bundle exec aws_lambda_ric "app.LambdaFunction::Handler.process" + ''; + + simulateLambda = pkgs.writeShellScriptBin "simulateLambda" '' + ${pkgs.aws-lambda-rie}/bin/aws-lambda-rie ${startLambda}/bin/startLambda + ''; + + lambdaImage = + let + appLayer = { + name = "app-layer"; + path = [ startLambda zmkCompileScript ]; + }; + in + ociTools.makeSimpleImage { + name = "zmk-builder-lambda"; + layers = [ baseLayer depsLayer appLayer ]; + config = { + User = "deploy"; + WorkingDir = "/tmp"; + Entrypoint = [ "${entrypoint}/bin/entrypoint" ]; + Cmd = [ "startLambda" ]; + Env = [ "CCACHE_DIR=/tmp/ccache" "REVISION=${revision}" ]; + }; + }; +in { + inherit lambdaImage zmkCompileScript dts2yml ccacheCache; + directLambdaImage = lambdaImage; + + # nix shell -f release.nix simulateLambda -c simulateLambda + inherit simulateLambda; +} From 2fad527cc5abed5bb59b4d4a4b0ee511d0e514e9 Mon Sep 17 00:00:00 2001 From: moergo-sc Date: Sun, 19 Sep 2021 13:05:21 +1200 Subject: [PATCH 09/23] Configure Glove80 board definitions for custom features Additionally adds a board definition for Glove80-v0, an early version of Glove80. --- app/boards/arm/glove80/glove80.keymap | 35 ++++- app/boards/arm/glove80/glove80_lh.dts | 27 ++++ app/boards/arm/glove80/glove80_lh_defconfig | 21 +++ app/boards/arm/glove80/glove80_rh_defconfig | 10 +- app/boards/arm/glove80_v0/Kconfig | 7 + app/boards/arm/glove80_v0/Kconfig.board | 12 ++ app/boards/arm/glove80_v0/Kconfig.defconfig | 66 +++++++++ app/boards/arm/glove80_v0/board.cmake | 5 + app/boards/arm/glove80_v0/glove80_v0.dtsi | 100 +++++++++++++ app/boards/arm/glove80_v0/glove80_v0.keymap | 56 ++++++++ .../arm/glove80_v0/glove80_v0_lh-pinctrl.dtsi | 47 ++++++ app/boards/arm/glove80_v0/glove80_v0_lh.dts | 135 ++++++++++++++++++ .../arm/glove80_v0/glove80_v0_lh.keymap | 1 + app/boards/arm/glove80_v0/glove80_v0_lh.yaml | 15 ++ app/boards/arm/glove80_v0/glove80_v0_lh.yml | 10 ++ .../arm/glove80_v0/glove80_v0_lh_defconfig | 50 +++++++ .../arm/glove80_v0/glove80_v0_rh-pinctrl.dtsi | 47 ++++++ app/boards/arm/glove80_v0/glove80_v0_rh.dts | 118 +++++++++++++++ .../arm/glove80_v0/glove80_v0_rh.keymap | 1 + app/boards/arm/glove80_v0/glove80_v0_rh.yaml | 15 ++ app/boards/arm/glove80_v0/glove80_v0_rh.yml | 10 ++ .../arm/glove80_v0/glove80_v0_rh_defconfig | 50 +++++++ 22 files changed, 833 insertions(+), 5 deletions(-) create mode 100644 app/boards/arm/glove80_v0/Kconfig create mode 100644 app/boards/arm/glove80_v0/Kconfig.board create mode 100644 app/boards/arm/glove80_v0/Kconfig.defconfig create mode 100644 app/boards/arm/glove80_v0/board.cmake create mode 100644 app/boards/arm/glove80_v0/glove80_v0.dtsi create mode 100644 app/boards/arm/glove80_v0/glove80_v0.keymap create mode 100644 app/boards/arm/glove80_v0/glove80_v0_lh-pinctrl.dtsi create mode 100644 app/boards/arm/glove80_v0/glove80_v0_lh.dts create mode 100644 app/boards/arm/glove80_v0/glove80_v0_lh.keymap create mode 100644 app/boards/arm/glove80_v0/glove80_v0_lh.yaml create mode 100644 app/boards/arm/glove80_v0/glove80_v0_lh.yml create mode 100644 app/boards/arm/glove80_v0/glove80_v0_lh_defconfig create mode 100644 app/boards/arm/glove80_v0/glove80_v0_rh-pinctrl.dtsi create mode 100644 app/boards/arm/glove80_v0/glove80_v0_rh.dts create mode 100644 app/boards/arm/glove80_v0/glove80_v0_rh.keymap create mode 100644 app/boards/arm/glove80_v0/glove80_v0_rh.yaml create mode 100644 app/boards/arm/glove80_v0/glove80_v0_rh.yml create mode 100644 app/boards/arm/glove80_v0/glove80_v0_rh_defconfig diff --git a/app/boards/arm/glove80/glove80.keymap b/app/boards/arm/glove80/glove80.keymap index 60129bd94e9..6806be79f97 100644 --- a/app/boards/arm/glove80/glove80.keymap +++ b/app/boards/arm/glove80/glove80.keymap @@ -11,10 +11,13 @@ #include #include +#define HYPER LC(LS(LG(LALT))) + // layers #define DEFAULT 0 #define LOWER 1 #define MAGIC 2 +#define FACTORY_TEST 3 / { behaviors { @@ -26,9 +29,24 @@ tapping-term-ms = <200>; bindings = <&mo LOWER>, <&to LOWER>; }; + + magic: magic_hold_tap { + compatible = "zmk,behavior-hold-tap"; + #binding-cells = <2>; + flavor = "tap-preferred"; + tapping-term-ms = <200>; + bindings = <&mo>, <&rgb_ug_status_macro>; + }; }; macros { + rgb_ug_status_macro: rgb_ug_status_macro_0 { + compatible = "zmk,behavior-macro"; + #binding-cells = <0>; + bindings + = <&rgb_ug RGB_STATUS>; + }; + bt_0: bt_profile_macro_0 { compatible = "zmk,behavior-macro"; #binding-cells = <0>; @@ -80,7 +98,7 @@ &kp TAB &kp Q &kp W &kp E &kp R &kp T &kp Y &kp U &kp I &kp O &kp P &kp BSLH &kp ESC &kp A &kp S &kp D &kp F &kp G &kp H &kp J &kp K &kp L &kp SEMI &kp SQT &kp GRAVE &kp Z &kp X &kp C &kp V &kp B &kp LSHFT &kp LCTRL &layer_td &kp LGUI &kp RCTRL &kp RSHFT &kp N &kp M &kp COMMA &kp DOT &kp FSLH &kp PG_UP - &mo MAGIC &kp HOME &kp END &kp LEFT &kp RIGHT &kp BSPC &kp DEL &kp LALT &kp RALT &kp RET &kp SPACE &kp UP &kp DOWN &kp LBKT &kp RBKT &kp PG_DN + &magic MAGIC 0 &kp HOME &kp END &kp LEFT &kp RIGHT &kp BSPC &kp DEL &kp LALT &kp RALT &kp RET &kp SPACE &kp UP &kp DOWN &kp LBKT &kp RBKT &kp PG_DN >; }; @@ -97,12 +115,23 @@ magic_layer { bindings = < - &bt BT_CLR &none &none &none &none &none &none &none &none &none + &bt BT_CLR &none &none &none &none &none &none &none &none &bt BT_CLR_ALL &none &none &none &none &none &none &none &none &none &none &none &none &none &rgb_ug RGB_SPI &rgb_ug RGB_SAI &rgb_ug RGB_HUI &rgb_ug RGB_BRI &rgb_ug RGB_TOG &none &none &none &none &none &none &bootloader &rgb_ug RGB_SPD &rgb_ug RGB_SAD &rgb_ug RGB_HUD &rgb_ug RGB_BRD &rgb_ug RGB_EFF &none &none &none &none &none &bootloader &sys_reset &none &none &none &none &none &bt_2 &bt_3 &none &none &none &none &none &none &none &none &none &sys_reset - &none &none &none &none &none &bt_0 &bt_1 &out OUT_USB &none &none &none &none &none &none &none &none + &none &none &none &none &none &bt_0 &bt_1 &out OUT_USB &none &none &none &none &none &none &none &to FACTORY_TEST + >; + }; + + factory_test_layer { + bindings = < + &kp N0 &kp N6 &kp N2 &kp N8 &kp N4 &kp N4 &kp N8 &kp N2 &kp N6 &kp N0 + &kp N1 &kp N7 &kp N3 &kp N9 &kp N5 &kp N0 &kp N0 &kp N5 &kp N9 &kp N3 &kp N7 &kp N1 + &kp N2 &kp N8 &kp N4 &kp N0 &kp N6 &kp N1 &kp N1 &kp N6 &kp N0 &kp N4 &kp N8 &kp N2 + &kp N3 &kp N9 &kp N5 &kp N1 &kp N7 &kp N2 &kp N2 &kp N7 &kp N1 &kp N5 &kp N9 &kp N3 + &kp N4 &kp N0 &kp N6 &kp N2 &kp N8 &kp N3 &kp N4 &kp N5 &kp N6 &kp N6 &kp N5 &kp N4 &kp N3 &kp N8 &kp N2 &kp N6 &kp N0 &kp N4 + &kp N5 &kp N1 &kp N7 &kp N3 &kp N9 &kp N7 &kp N8 &kp N9 &kp N9 &kp N8 &kp N7 &kp N9 &kp N3 &kp N7 &kp N1 &kp N5 >; }; }; diff --git a/app/boards/arm/glove80/glove80_lh.dts b/app/boards/arm/glove80/glove80_lh.dts index 5ef54207127..43b620a3cbc 100644 --- a/app/boards/arm/glove80/glove80_lh.dts +++ b/app/boards/arm/glove80/glove80_lh.dts @@ -17,6 +17,7 @@ zmk,underglow = &led_strip; zmk,backlight = &back_led_backlight; zmk,battery = &vbatt; + zmk,underglow-indicators = &underglow_indicators; }; back_led_backlight: pwmleds { @@ -36,6 +37,32 @@ vbatt: vbatt { compatible = "zmk,battery-nrf-vddh"; }; + +/* + MoErgo 40 LEDs + + 34 28 22 16 10 + 35 29 23 17 11 6 + 36 30 24 18 12 7 + 37 31 25 19 13 8 + 38 32 26 20 14 9 + 39 33 27 21 15 + 0 1 2 + 3 4 5 +*/ + + underglow_indicators: underglow-indicators { + compatible = "zmk,underglow-indicators"; + layer-state = <35 29 23 17 11 6>; + bat-lhs = <36 30 24 18 12 7>; + bat-rhs = <37 31 25 19 13 8>; + capslock = <22>; + numlock = <16>; + scrolllock = <10>; + ble-state = <3 4 0 1>; + usb-state = <5>; + output-fallback = <15>; + }; }; &spi3 { diff --git a/app/boards/arm/glove80/glove80_lh_defconfig b/app/boards/arm/glove80/glove80_lh_defconfig index b454e8be1bf..57c72c8be9d 100644 --- a/app/boards/arm/glove80/glove80_lh_defconfig +++ b/app/boards/arm/glove80/glove80_lh_defconfig @@ -23,11 +23,25 @@ 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 @@ -53,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 @@ -81,6 +98,10 @@ 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 + # 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_defconfig b/app/boards/arm/glove80/glove80_rh_defconfig index ef29d682a54..4394bee2ad2 100644 --- a/app/boards/arm/glove80/glove80_rh_defconfig +++ b/app/boards/arm/glove80/glove80_rh_defconfig @@ -5,8 +5,8 @@ CONFIG_SOC_SERIES_NRF52X=y CONFIG_SOC_NRF52840_QIAA=y CONFIG_BOARD_GLOVE80_RH=y -# Enable both USB and BLE -CONFIG_ZMK_USB=y +# Enable BLE for split peripheral +CONFIG_ZMK_USB=n CONFIG_ZMK_BLE=y # Keyboard IDs @@ -21,6 +21,9 @@ CONFIG_BT_DIS_PNP_VID=0x16c0 CONFIG_BT_DIS_MANUF="MoErgo" CONFIG_BT_DIS_MODEL="Glove80 Right" +### Bluetooth configuration workarounds + +# Use higher radio transmit power CONFIG_BT_CTLR_TX_PWR_PLUS_8=y # Enable MPU @@ -48,6 +51,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 diff --git a/app/boards/arm/glove80_v0/Kconfig b/app/boards/arm/glove80_v0/Kconfig new file mode 100644 index 00000000000..f971e6c81da --- /dev/null +++ b/app/boards/arm/glove80_v0/Kconfig @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: MIT + +config BOARD_ENABLE_DCDC + bool "Enable DCDC mode" + select SOC_DCDC_NRF52X + default y + depends on (BOARD_GLOVE80_V0_LH || BOARD_GLOVE80_V0_RH) diff --git a/app/boards/arm/glove80_v0/Kconfig.board b/app/boards/arm/glove80_v0/Kconfig.board new file mode 100644 index 00000000000..ce97e890fbc --- /dev/null +++ b/app/boards/arm/glove80_v0/Kconfig.board @@ -0,0 +1,12 @@ +# nice!nano board configuration + +# Copyright (c) 2020 Pete Johanson +# SPDX-License-Identifier: MIT + +config BOARD_GLOVE80_V0_LH + bool "Glove80 v0 LH" + depends on SOC_NRF52840_QIAA + +config BOARD_GLOVE80_V0_RH + bool "Glove80 v0 RH" + depends on SOC_NRF52840_QIAA diff --git a/app/boards/arm/glove80_v0/Kconfig.defconfig b/app/boards/arm/glove80_v0/Kconfig.defconfig new file mode 100644 index 00000000000..9e68b18dc89 --- /dev/null +++ b/app/boards/arm/glove80_v0/Kconfig.defconfig @@ -0,0 +1,66 @@ +# Copyright (c) 2021 The ZMK Contributors +# SPDX-License-Identifier: MIT + +if BOARD_GLOVE80_V0_LH +config BOARD + default "glove80 v0 lh" + +config ZMK_SPLIT_BLE_ROLE_CENTRAL + default y + +config ZMK_KEYBOARD_NAME + default "Glove80 V0 Left" +endif # BOARD_GLOVE80_V0_LH + +if BOARD_GLOVE80_V0_RH +config BOARD + default "glove80 v0 rh" + +config ZMK_KEYBOARD_NAME + default "Glove80 V0 Right" +endif # BOARD_GLOVE80_V0_RH + +if BOARD_GLOVE80_V0_LH || BOARD_GLOVE80_V0_RH + +config ZMK_SPLIT + default y + +config BT_CTLR + default BT + +config ZMK_BLE + default y + +config ZMK_USB + default y + +config ZMK_BATTERY_VOLTAGE_DIVIDER + default y + +config ZMK_BATTERY_NRF_VDDH + default y + +config PINCTRL + default y + +if USB + +config USB_NRFX + default y + +config USB_DEVICE_STACK + default y + +endif # USB + +if ZMK_BACKLIGHT + +config PWM + default y + +config LED_PWM + default y + +endif # ZMK_BACKLIGHT + +endif # BOARD_GLOVE80_V0_LH || BOARD_GLOVE80_V0_RH diff --git a/app/boards/arm/glove80_v0/board.cmake b/app/boards/arm/glove80_v0/board.cmake new file mode 100644 index 00000000000..fa847d50595 --- /dev/null +++ b/app/boards/arm/glove80_v0/board.cmake @@ -0,0 +1,5 @@ +# SPDX-License-Identifier: MIT + +board_runner_args(nrfjprog "--nrf-family=NRF52" "--softreset") +include(${ZEPHYR_BASE}/boards/common/blackmagicprobe.board.cmake) +include(${ZEPHYR_BASE}/boards/common/nrfjprog.board.cmake) diff --git a/app/boards/arm/glove80_v0/glove80_v0.dtsi b/app/boards/arm/glove80_v0/glove80_v0.dtsi new file mode 100644 index 00000000000..dafa089dbd8 --- /dev/null +++ b/app/boards/arm/glove80_v0/glove80_v0.dtsi @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2020 Pete Johanson + * + * SPDX-License-Identifier: MIT + */ + +#include + +/ { + chosen { + zmk,kscan = &kscan0; + zmk,matrix-transform = &default_transform; + zephyr,code-partition = &code_partition; + zephyr,sram = &sram0; + zephyr,flash = &flash0; + zephyr,console = &cdc_acm_uart; + }; + + default_transform: keymap_transform_0 { + compatible = "zmk,matrix-transform"; + columns = <14>; + rows = <6>; + map = < +RC(0,0) RC(0,1) RC(0,2) RC(0,3) RC(0,4) RC(0,9) RC(0,10) RC(0,11) RC(0,12) RC(0,13) +RC(1,0) RC(1,1) RC(1,2) RC(1,3) RC(1,4) RC(1,5) RC(1,8) RC(1,9) RC(1,10) RC(1,11) RC(1,12) RC(1,13) +RC(2,0) RC(2,1) RC(2,2) RC(2,3) RC(2,4) RC(2,5) RC(2,8) RC(2,9) RC(2,10) RC(2,11) RC(2,12) RC(2,13) +RC(3,0) RC(3,1) RC(3,2) RC(3,3) RC(3,4) RC(3,5) RC(3,8) RC(3,9) RC(3,10) RC(3,11) RC(3,12) RC(3,13) +RC(4,0) RC(4,1) RC(4,2) RC(4,3) RC(4,4) RC(4,5) RC(0,6) RC(1,6) RC(2,6) RC(2,7) RC(1,7) RC(0,7) RC(4,8) RC(4,9) RC(4,10) RC(4,11) RC(4,12) RC(4,13) +RC(5,0) RC(5,1) RC(5,2) RC(5,3) RC(5,4) RC(3,6) RC(4,6) RC(5,6) RC(5,7) RC(4,7) RC(3,7) RC(5,9) RC(5,10) RC(5,11) RC(5,12) RC(5,13) + >; + }; + + kscan0: kscan { + compatible = "zmk,kscan-gpio-matrix"; + + diode-direction = "row2col"; + debounce-press-ms = <1>; + debounce-release-ms = <25>; + }; + +}; + +&adc { + status = "okay"; +}; + +&gpiote { + status = "okay"; +}; + +&gpio0 { + status = "okay"; +}; + +&gpio1 { + status = "okay"; +}; + +&usbd { + status = "okay"; + cdc_acm_uart: cdc_acm_uart { + compatible = "zephyr,cdc-acm-uart"; + }; +}; + +&flash0 { + /* + * For more information, see: + * http://docs.zephyrproject.org/latest/devices/dts/flash_partitions.html + */ + partitions { + compatible = "fixed-partitions"; + #address-cells = <1>; + #size-cells = <1>; + + sd_partition: partition@0 { + reg = <0x00000000 0x00026000>; + }; + code_partition: partition@26000 { + reg = <0x00026000 0x000c6000>; + }; + + /* + * The flash starting at 0x000ec000 and ending at + * 0x000f3fff is reserved for use by the application. + */ + + /* + * Storage partition will be used by FCB/LittleFS/NVS + * if enabled. + */ + storage_partition: partition@ec000 { + reg = <0x000ec000 0x00008000>; + }; + + boot_partition: partition@f4000 { + reg = <0x000f4000 0x0000c000>; + }; + }; +}; diff --git a/app/boards/arm/glove80_v0/glove80_v0.keymap b/app/boards/arm/glove80_v0/glove80_v0.keymap new file mode 100644 index 00000000000..52a88125275 --- /dev/null +++ b/app/boards/arm/glove80_v0/glove80_v0.keymap @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2020 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#include +#include +#include +#include +#include +#include + +#define HYPER LC(LS(LG(LALT))) + +// layers +#define DEFAULT 0 +#define LOWER 1 + +/ { + keymap { + compatible = "zmk,keymap"; + + default_layer { + // --------------------------------------------------------------------------------------------------------------------------------- + // | F1 | F2 | F3 | F4 | F5 | | F6 | F7 | F8 | F9 | F10 | + // | = | 1 | 2 | 3 | 4 | 5 | | 6 | 7 | 8 | 9 | 0 | - | + // | TAB | Q | W | E | R | T | | Y | U | I | O | P | \ | + // | ESC | A | S | D | F | G | | H | J | K | L | ; | ' | + // | ` | Z | X | C | V | B | LSHFT | LCTRL | LOWER | | LGUI | RCTRL | RSHFT | N | M | , | . | / | PGUP | + // | MAGIC | HOME| END | LEFT | RIGHT| | BSPC | DEL | LALT | | RALT | RET | SPACE | | UP | DOWN | [ | ] | PGDN | + + + // MAGIC is currently bound to the same as LAYER - these will be fixed later + bindings = < + &kp F1 &kp F2 &kp F3 &kp F4 &kp F5 &kp F6 &kp F7 &kp F8 &kp F9 &kp F10 + &kp EQUAL &kp N1 &kp N2 &kp N3 &kp N4 &kp N5 &kp N6 &kp N7 &kp N8 &kp N9 &kp N0 &kp MINUS + &kp TAB &kp Q &kp W &kp E &kp R &kp T &kp Y &kp U &kp I &kp O &kp P &kp BSLH + &kp ESC &kp A &kp S &kp D &kp F &kp G &kp H &kp J &kp K &kp L &kp SEMI &kp SQT + &kp GRAVE &kp Z &kp X &kp C &kp V &kp B &kp LSHFT &kp LCTRL &mo LOWER &kp LGUI &kp RCTRL &kp RSHFT &kp N &kp M &kp COMMA &kp DOT &kp FSLH &kp PG_UP + &mo LOWER &kp HOME &kp END &kp LEFT &kp RIGHT &kp BSPC &kp DEL &kp LALT &kp RALT &kp RET &kp SPACE &kp UP &kp DOWN &kp LBKT &kp RBKT &kp PG_DN + >; + }; + + lower_layer { + bindings = < + &bt BT_SEL 0 &bt BT_SEL 1 &bt BT_SEL 2 &bt BT_SEL 3 &bt BT_SEL 4 &none &none &none &kp F11 &kp F12 + &bt BT_CLR &bt BT_CLR &out OUT_USB &out OUT_BLE &out OUT_TOG &none &none &none &none &none &none &none + &bootloader &rgb_ug RGB_TOG &rgb_ug RGB_EFF &rgb_ug RGB_BRI &rgb_ug RGB_BRD &kp K_VOL_UP &none &none &none &none &none &bootloader + &sys_reset &none &none &none &none &kp K_VOL_DN &none &none &none &none &none &sys_reset + &ext_power EP_ON &ext_power EP_OFF &none &none &none &kp K_MUTE &none &kp HYPER &none &none &none &none &none &none &none &none &none &none + &none &kp CAPS &kp INS &none &none &none &none &none &none &none &none &none &none &none &none &none + >; + }; + }; +}; diff --git a/app/boards/arm/glove80_v0/glove80_v0_lh-pinctrl.dtsi b/app/boards/arm/glove80_v0/glove80_v0_lh-pinctrl.dtsi new file mode 100644 index 00000000000..f4437b601d2 --- /dev/null +++ b/app/boards/arm/glove80_v0/glove80_v0_lh-pinctrl.dtsi @@ -0,0 +1,47 @@ +&pinctrl { + spi1_default: spi1_default { + group1 { + psels = , + , + ; + }; + }; + + spi1_sleep: spi1_sleep { + group1 { + psels = , + , + ; + low-power-enable; + }; + }; + + pwm0_default: pwm0_default { + group1 { + psels = ; + }; + }; + + pwm0_sleep: pwm0_sleep { + group1 { + psels = ; + low-power-enable; + }; + }; + + uart0_default: uart0_default { + group1 { + psels = , + ; + }; + }; + + uart0_sleep: uart0_sleep { + group1 { + psels = , + ; + low-power-enable; + }; + }; + +}; diff --git a/app/boards/arm/glove80_v0/glove80_v0_lh.dts b/app/boards/arm/glove80_v0/glove80_v0_lh.dts new file mode 100644 index 00000000000..db8ae5a3334 --- /dev/null +++ b/app/boards/arm/glove80_v0/glove80_v0_lh.dts @@ -0,0 +1,135 @@ +/* + * Copyright (c) 2021 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +/dts-v1/; +#include +#include +#include "glove80_v0.dtsi" +#include "glove80_v0_lh-pinctrl.dtsi" + +/ { + model = "glove80_v0_lh"; + compatible = "glove80_v0_lh"; + + chosen { + zmk,underglow = &led_strip; + zmk,backlight = &power_led_backlight; + zmk,battery = &vbatt; + zmk,underglow-indicators = &underglow_indicators; + }; + + power_led_backlight: pwmleds { + compatible = "pwm-leds"; + pwm_led_0 { + pwms = <&pwm0 0 PWM_USEC(20) PWM_POLARITY_NORMAL>; + }; + }; + + // Node name must match original "EXT_POWER" label to preserve user settings. + EXT_POWER { + compatible = "zmk,ext-power-generic"; + control-gpios = <&gpio0 30 GPIO_ACTIVE_HIGH>; /**[SC] WS2812_CE */ + init-delay-ms = <10>; + }; + + vbatt: vbatt-divider { + compatible = "zmk,battery-voltage-divider"; + io-channels = <&adc 5>; + output-ohms = <820000>; /** Double check that this is right */ + full-ohms = <(1500000 + 820000)>; + }; + + vbatt-vddh { + compatible = "zmk,battery-nrf-vddh"; + }; + +/* + MoErgo 40 LEDs + + 34 28 22 16 10 + 35 29 23 17 11 6 + 36 30 24 18 12 7 + 37 31 25 19 13 8 + 38 32 26 20 14 9 + 39 33 27 21 15 + 0 1 2 + 3 4 5 +*/ + + underglow_indicators: underglow-indicators { + compatible = "zmk,underglow-indicators"; + layer-state = <35 29 23 17 11 6>; + bat-lhs = <36 30 24 18 12 7>; + bat-rhs = <37 31 25 19 13 8>; + capslock = <22>; + numlock = <16>; + scrolllock = <10>; + ble-state = <3 4 0 1>; + usb-state = <5>; + output-fallback = <15>; + }; +}; + +&spi1 { + compatible = "nordic,nrf-spim"; + /* Cannot be used together with i2c0. */ + status = "okay"; + // Unused pins, needed for SPI definition, but not used by the ws2812 driver itself. + + pinctrl-0 = <&spi1_default>; + pinctrl-1 = <&spi1_sleep>; + pinctrl-names = "default", "sleep"; + led_strip: ws2812@0 { + compatible = "worldsemi,ws2812-spi"; + + /* SPI */ + reg = <0>; /* ignored, but necessary for SPI bindings */ + spi-max-frequency = <2000000>; + + /* WS2812 */ + chain-length = <40>; /* 18 keys have underglow at the moment */ + spi-one-frame = <0x70>; + spi-zero-frame = <0x40>; + + color-mapping = ; + }; +}; + +&pwm0 { + status = "okay"; + pinctrl-0 = <&pwm0_default>; + pinctrl-1 = <&pwm0_sleep>; + pinctrl-names = "default", "sleep"; +}; + + +// [SC] Not sure if this section is necessary +&uart0 { + compatible = "nordic,nrf-uarte"; + pinctrl-0 = <&uart0_default>; + pinctrl-1 = <&uart0_sleep>; + pinctrl-names = "default", "sleep"; +}; + +&kscan0 { + row-gpios + = <&gpio0 26 GPIO_ACTIVE_HIGH> // LH ROW1 + , <&gpio0 5 GPIO_ACTIVE_HIGH> // LH ROW2 + , <&gpio0 7 GPIO_ACTIVE_HIGH> // LH ROW3 + , <&gpio1 8 GPIO_ACTIVE_HIGH> // LH ROW4 + , <&gpio0 11 GPIO_ACTIVE_HIGH> // LH ROW5 + , <&gpio0 12 GPIO_ACTIVE_HIGH> // LH ROW6 + ; + col-gpios + = <&gpio1 1 (GPIO_ACTIVE_HIGH | GPIO_PULL_DOWN)> // LH COL6 + , <&gpio1 3 (GPIO_ACTIVE_HIGH | GPIO_PULL_DOWN)> // LH COL5 + , <&gpio1 5 (GPIO_ACTIVE_HIGH | GPIO_PULL_DOWN)> // LH COL4 + , <&gpio1 7 (GPIO_ACTIVE_HIGH | GPIO_PULL_DOWN)> // LH COL3 + , <&gpio1 6 (GPIO_ACTIVE_HIGH | GPIO_PULL_DOWN)> // LH COL2 + , <&gpio1 4 (GPIO_ACTIVE_HIGH | GPIO_PULL_DOWN)> // LH COL1 + , <&gpio0 2 (GPIO_ACTIVE_HIGH | GPIO_PULL_DOWN)> // LH Thumb + ; +}; diff --git a/app/boards/arm/glove80_v0/glove80_v0_lh.keymap b/app/boards/arm/glove80_v0/glove80_v0_lh.keymap new file mode 100644 index 00000000000..1a5a04b3937 --- /dev/null +++ b/app/boards/arm/glove80_v0/glove80_v0_lh.keymap @@ -0,0 +1 @@ +#include "glove80_v0.keymap" \ No newline at end of file diff --git a/app/boards/arm/glove80_v0/glove80_v0_lh.yaml b/app/boards/arm/glove80_v0/glove80_v0_lh.yaml new file mode 100644 index 00000000000..56b575ac27e --- /dev/null +++ b/app/boards/arm/glove80_v0/glove80_v0_lh.yaml @@ -0,0 +1,15 @@ +identifier: glove80_v0_lh +name: Glove80_V0_LH +type: mcu +arch: arm +toolchain: + - zephyr + - gnuarmemb + - xtools +supported: + - adc + - usb_device + - ble + - ieee802154 + - pwm + - watchdog diff --git a/app/boards/arm/glove80_v0/glove80_v0_lh.yml b/app/boards/arm/glove80_v0/glove80_v0_lh.yml new file mode 100644 index 00000000000..407021c51df --- /dev/null +++ b/app/boards/arm/glove80_v0/glove80_v0_lh.yml @@ -0,0 +1,10 @@ +file_format: "1" +id: glove80_v0_lh +name: Glove80_V0_LH +type: board +arch: arm +outputs: + - usb + - ble +url: https://www.moergo.com +exposes: diff --git a/app/boards/arm/glove80_v0/glove80_v0_lh_defconfig b/app/boards/arm/glove80_v0/glove80_v0_lh_defconfig new file mode 100644 index 00000000000..4d5676a1231 --- /dev/null +++ b/app/boards/arm/glove80_v0/glove80_v0_lh_defconfig @@ -0,0 +1,50 @@ +# SPDX-License-Identifier: MIT + +CONFIG_SOC_SERIES_NRF52X=y +CONFIG_SOC_NRF52840_QIAA=y +CONFIG_BOARD_GLOVE80_V0_LH=y + +# Enable MPU +CONFIG_ARM_MPU=y + +# enable GPIO +CONFIG_GPIO=y + +CONFIG_BUILD_OUTPUT_UF2=y + +CONFIG_USE_DT_CODE_PARTITION=y + +CONFIG_MPU_ALLOW_FLASH_WRITE=y +CONFIG_NVS=y +CONFIG_SETTINGS_NVS=y +CONFIG_FLASH=y +CONFIG_FLASH_PAGE_LAYOUT=y +CONFIG_FLASH_MAP=y + +# Enable RGB underglow +CONFIG_ZMK_RGB_UNDERGLOW=y +CONFIG_WS2812_STRIP=y +CONFIG_SPI=y + +CONFIG_ZMK_RGB_UNDERGLOW_ON_START=n +CONFIG_ZMK_RGB_UNDERGLOW_EXT_POWER=y +CONFIG_ZMK_RGB_UNDERGLOW_BRT_STEP=4 + +CONFIG_ZMK_RGB_UNDERGLOW_EFF_START=3 +CONFIG_ZMK_RGB_UNDERGLOW_HUE_START=285 +CONFIG_ZMK_RGB_UNDERGLOW_SAT_START=75 +CONFIG_ZMK_RGB_UNDERGLOW_BRT_START=20 + +# The power LED is implemented as a backlight +CONFIG_ZMK_BACKLIGHT=y +CONFIG_ZMK_BACKLIGHT_ON_START=y +CONFIG_ZMK_BACKLIGHT_BRT_START=5 +CONFIG_ZMK_BACKLIGHT_AUTO_OFF_IDLE=y +CONFIG_ZMK_BACKLIGHT_AUTO_OFF_USB=y + +# Turn on logging, and set ZMK logging to debug output +# Only for debugging +CONFIG_ZMK_USB_LOGGING=n + +# Turn on debugging to disable optimization +CONFIG_DEBUG=n diff --git a/app/boards/arm/glove80_v0/glove80_v0_rh-pinctrl.dtsi b/app/boards/arm/glove80_v0/glove80_v0_rh-pinctrl.dtsi new file mode 100644 index 00000000000..08f649f759f --- /dev/null +++ b/app/boards/arm/glove80_v0/glove80_v0_rh-pinctrl.dtsi @@ -0,0 +1,47 @@ +&pinctrl { + spi1_default: spi1_default { + group1 { + psels = , + , + ; + }; + }; + + spi1_sleep: spi1_sleep { + group1 { + psels = , + , + ; + low-power-enable; + }; + }; + + pwm0_default: pwm0_default { + group1 { + psels = ; + }; + }; + + pwm0_sleep: pwm0_sleep { + group1 { + psels = ; + low-power-enable; + }; + }; + + uart0_default: uart0_default { + group1 { + psels = , + ; + }; + }; + + uart0_sleep: uart0_sleep { + group1 { + psels = , + ; + low-power-enable; + }; + }; + +}; diff --git a/app/boards/arm/glove80_v0/glove80_v0_rh.dts b/app/boards/arm/glove80_v0/glove80_v0_rh.dts new file mode 100644 index 00000000000..c2185da7817 --- /dev/null +++ b/app/boards/arm/glove80_v0/glove80_v0_rh.dts @@ -0,0 +1,118 @@ +/* + * Copyright (c) 2021 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +/dts-v1/; +#include +#include +#include "glove80_v0.dtsi" +#include "glove80_v0_rh-pinctrl.dtsi" + +/ { + model = "glove80_v0_rh"; + compatible = "glove80_v0_rh"; + + chosen { + zmk,underglow = &led_strip; + zmk,backlight = &power_led_backlight; + zmk,battery = &vbatt; + }; + + leds { + compatible = "gpio-leds"; + red_led: led_0 { + gpios = <&gpio0 20 GPIO_ACTIVE_HIGH>; + }; + }; + + power_led_backlight: pwmleds { + compatible = "pwm-leds"; + pwm_led_0 { + pwms = <&pwm0 0 PWM_USEC(20) PWM_POLARITY_NORMAL>; + }; + }; + + // Node name must match original "EXT_POWER" label to preserve user settings. + EXT_POWER { + compatible = "zmk,ext-power-generic"; + control-gpios = <&gpio0 21 GPIO_ACTIVE_HIGH>; /**[SC] WS2812_CE */ + init-delay-ms = <10>; + }; + + vbatt: vbatt-divider { + compatible = "zmk,battery-voltage-divider"; + io-channels = <&adc 5>; + output-ohms = <820000>; /** Double check that this is right */ + full-ohms = <(1500000 + 820000)>; + }; + + vbatt-vddh { + compatible = "zmk,battery-nrf-vddh"; + }; +}; + +&spi1 { + compatible = "nordic,nrf-spim"; + /* Cannot be used together with i2c0. */ + status = "okay"; + // Unused pins, needed for SPI definition, but not used by the ws2812 driver itself. + + pinctrl-0 = <&spi1_default>; + pinctrl-1 = <&spi1_sleep>; + pinctrl-names = "default", "sleep"; + led_strip: ws2812@0 { + compatible = "worldsemi,ws2812-spi"; + + /* SPI */ + reg = <0>; /* ignored, but necessary for SPI bindings */ + spi-max-frequency = <2000000>; + + /* WS2812 */ + chain-length = <40>; + spi-one-frame = <0x70>; + spi-zero-frame = <0x40>; + + color-mapping = ; + }; +}; + +&pwm0 { + status = "okay"; + pinctrl-0 = <&pwm0_default>; + pinctrl-1 = <&pwm0_sleep>; + pinctrl-names = "default", "sleep"; +}; + +// [SC] Not sure if this section is necessary +&uart0 { + compatible = "nordic,nrf-uarte"; + pinctrl-0 = <&uart0_default>; + pinctrl-1 = <&uart0_sleep>; + pinctrl-names = "default", "sleep"; +}; + +&default_transform { + col-offset = <7>; +}; + +&kscan0 { + row-gpios + = <&gpio0 26 GPIO_ACTIVE_HIGH> // RH ROW1 + , <&gpio0 5 GPIO_ACTIVE_HIGH> // RH ROW2 + , <&gpio0 7 GPIO_ACTIVE_HIGH> // RH ROW3 + , <&gpio1 8 GPIO_ACTIVE_HIGH> // RH ROW4 + , <&gpio0 11 GPIO_ACTIVE_HIGH> // RH ROW5 + , <&gpio0 12 GPIO_ACTIVE_HIGH> // RH ROW6 + ; + col-gpios + = <&gpio0 2 (GPIO_ACTIVE_HIGH | GPIO_PULL_DOWN)> // RH Thumb + , <&gpio1 4 (GPIO_ACTIVE_HIGH | GPIO_PULL_DOWN)> // RH COL1 + , <&gpio1 6 (GPIO_ACTIVE_HIGH | GPIO_PULL_DOWN)> // RH COL2 + , <&gpio1 7 (GPIO_ACTIVE_HIGH | GPIO_PULL_DOWN)> // RH COL3 + , <&gpio1 5 (GPIO_ACTIVE_HIGH | GPIO_PULL_DOWN)> // RH COL4 + , <&gpio1 3 (GPIO_ACTIVE_HIGH | GPIO_PULL_DOWN)> // RH COL5 + , <&gpio1 1 (GPIO_ACTIVE_HIGH | GPIO_PULL_DOWN)> // RH COL6 + ; +}; diff --git a/app/boards/arm/glove80_v0/glove80_v0_rh.keymap b/app/boards/arm/glove80_v0/glove80_v0_rh.keymap new file mode 100644 index 00000000000..1a5a04b3937 --- /dev/null +++ b/app/boards/arm/glove80_v0/glove80_v0_rh.keymap @@ -0,0 +1 @@ +#include "glove80_v0.keymap" \ No newline at end of file diff --git a/app/boards/arm/glove80_v0/glove80_v0_rh.yaml b/app/boards/arm/glove80_v0/glove80_v0_rh.yaml new file mode 100644 index 00000000000..222ae1770e2 --- /dev/null +++ b/app/boards/arm/glove80_v0/glove80_v0_rh.yaml @@ -0,0 +1,15 @@ +identifier: glove80_v0_rh +name: Glove80_V0_RH +type: mcu +arch: arm +toolchain: + - zephyr + - gnuarmemb + - xtools +supported: + - adc + - usb_device + - ble + - ieee802154 + - pwm + - watchdog diff --git a/app/boards/arm/glove80_v0/glove80_v0_rh.yml b/app/boards/arm/glove80_v0/glove80_v0_rh.yml new file mode 100644 index 00000000000..41333f08662 --- /dev/null +++ b/app/boards/arm/glove80_v0/glove80_v0_rh.yml @@ -0,0 +1,10 @@ +file_format: "1" +id: glove80_v0_rh +name: Glove80_V0_RH +type: board +arch: arm +outputs: + - usb + - ble +url: https://www.moergo.com +exposes: diff --git a/app/boards/arm/glove80_v0/glove80_v0_rh_defconfig b/app/boards/arm/glove80_v0/glove80_v0_rh_defconfig new file mode 100644 index 00000000000..71b5f2f6215 --- /dev/null +++ b/app/boards/arm/glove80_v0/glove80_v0_rh_defconfig @@ -0,0 +1,50 @@ +# SPDX-License-Identifier: MIT + +CONFIG_SOC_SERIES_NRF52X=y +CONFIG_SOC_NRF52840_QIAA=y +CONFIG_BOARD_GLOVE80_V0_RH=y + +# Enable MPU +CONFIG_ARM_MPU=y + +# enable GPIO +CONFIG_GPIO=y + +CONFIG_BUILD_OUTPUT_UF2=y + +CONFIG_USE_DT_CODE_PARTITION=y + +CONFIG_MPU_ALLOW_FLASH_WRITE=y +CONFIG_NVS=y +CONFIG_SETTINGS_NVS=y +CONFIG_FLASH=y +CONFIG_FLASH_PAGE_LAYOUT=y +CONFIG_FLASH_MAP=y + +# Disable RGB underglow for now in RH +CONFIG_ZMK_RGB_UNDERGLOW=y +CONFIG_WS2812_STRIP=y +CONFIG_SPI=y + +CONFIG_ZMK_RGB_UNDERGLOW_ON_START=y +CONFIG_ZMK_RGB_UNDERGLOW_EXT_POWER=y +CONFIG_ZMK_RGB_UNDERGLOW_BRT_STEP=4 + +CONFIG_ZMK_RGB_UNDERGLOW_EFF_START=3 +CONFIG_ZMK_RGB_UNDERGLOW_HUE_START=285 +CONFIG_ZMK_RGB_UNDERGLOW_SAT_START=75 +CONFIG_ZMK_RGB_UNDERGLOW_BRT_START=20 + +# The power LED is implemented as a backlight +CONFIG_ZMK_BACKLIGHT=y +CONFIG_ZMK_BACKLIGHT_ON_START=y +CONFIG_ZMK_BACKLIGHT_BRT_START=5 +CONFIG_ZMK_BACKLIGHT_AUTO_OFF_IDLE=y +CONFIG_ZMK_BACKLIGHT_AUTO_OFF_USB=y + +# Turn on logging, and set ZMK logging to debug output +# Only for debugging +CONFIG_ZMK_USB_LOGGING=n + +# Turn on debugging to disable optimization +CONFIG_DEBUG=n From b4b3f7ab53d5905bd548ee41e13c9b0eab5d35fe Mon Sep 17 00:00:00 2001 From: darknao Date: Fri, 3 May 2024 14:44:08 +0200 Subject: [PATCH 10/23] valdur's mod --- app/include/zmk/split/bluetooth/central.h | 4 +- .../zmk/split/bluetooth/peripheral_layers.h | 4 + app/include/zmk/split/bluetooth/uuid.h | 1 + app/src/keymap.c | 1 + app/src/rgb_underglow.c | 146 +++++++++++++++++- app/src/split/bluetooth/CMakeLists.txt | 2 + app/src/split/bluetooth/central.c | 43 ++++++ app/src/split/bluetooth/peripheral_layers.c | 15 ++ app/src/split/bluetooth/service.c | 28 +++- 9 files changed, 241 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/central.h b/app/include/zmk/split/bluetooth/central.h index 5e9e09ff6a1..6e24b15cf8b 100644 --- a/app/include/zmk/split/bluetooth/central.h +++ b/app/include/zmk/split/bluetooth/central.h @@ -21,4 +21,6 @@ int zmk_split_bt_update_hid_indicator(zmk_hid_indicators_t indicators); int zmk_split_get_peripheral_battery_level(uint8_t source, uint8_t *level); -#endif // IS_ENABLED(CONFIG_ZMK_SPLIT_BLE_CENTRAL_BATTERY_LEVEL_FETCHING) \ No newline at end of file +#endif // IS_ENABLED(CONFIG_ZMK_SPLIT_BLE_CENTRAL_BATTERY_LEVEL_FETCHING) + +int zmk_split_bt_update_layers(uint32_t layers); 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 00000000000..e816cad0081 --- /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 dccdfc804c5..cdda41722b4 100644 --- a/app/include/zmk/split/bluetooth/uuid.h +++ b/app/include/zmk/split/bluetooth/uuid.h @@ -18,3 +18,4 @@ #define ZMK_SPLIT_BT_CHAR_RUN_BEHAVIOR_UUID ZMK_BT_SPLIT_UUID(0x00000002) #define ZMK_SPLIT_BT_CHAR_SENSOR_STATE_UUID ZMK_BT_SPLIT_UUID(0x00000003) #define ZMK_SPLIT_BT_UPDATE_HID_INDICATORS_UUID ZMK_BT_SPLIT_UUID(0x00000004) +#define ZMK_SPLIT_BT_UPDATE_LAYERS_UUID ZMK_BT_SPLIT_UUID(0x00000005) diff --git a/app/src/keymap.c b/app/src/keymap.c index 75a2dcbe64b..8c93c83be6d 100644 --- a/app/src/keymap.c +++ b/app/src/keymap.c @@ -95,6 +95,7 @@ static inline int set_layer_state(uint8_t layer, bool state) { if (ret < 0) { LOG_WRN("Failed to raise layer state changed (%d)", ret); } + zmk_split_bt_update_layers(_zmk_keymap_layer_state); } return ret; diff --git a/app/src/rgb_underglow.c b/app/src/rgb_underglow.c index 5bc6a8277da..3df029b2575 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) { @@ -197,6 +208,7 @@ static void zmk_led_write_pixels(void) { int bat0 = zmk_battery_state_of_charge(); int blend = 0; int reset_ext_power = 0; + if (state.status_active) { blend = zmk_led_generate_status(); } @@ -263,14 +275,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); @@ -282,6 +300,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); @@ -409,6 +428,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: @@ -423,6 +565,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(); @@ -581,7 +726,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 6e0ad617284..d448c0e5a5a 100644 --- a/app/src/split/bluetooth/CMakeLists.txt +++ b/app/src/split/bluetooth/CMakeLists.txt @@ -5,6 +5,8 @@ if (NOT CONFIG_ZMK_SPLIT_ROLE_CENTRAL) target_sources(app PRIVATE split_listener.c) target_sources(app PRIVATE service.c) target_sources(app PRIVATE peripheral.c) + 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 abb37a0b91c..fc0a7f57559 100644 --- a/app/src/split/bluetooth/central.c +++ b/app/src/split/bluetooth/central.c @@ -55,6 +55,8 @@ struct peripheral_slot { #if IS_ENABLED(CONFIG_ZMK_SPLIT_PERIPHERAL_HID_INDICATORS) uint16_t update_hid_indicators; #endif // IS_ENABLED(CONFIG_ZMK_SPLIT_PERIPHERAL_HID_INDICATORS) + uint16_t update_layers_handle; + uint8_t position_state[POSITION_STATE_DATA_LEN]; uint8_t changed_positions[POSITION_STATE_DATA_LEN]; }; @@ -143,6 +145,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; } @@ -447,6 +450,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)) { @@ -479,6 +486,8 @@ static uint8_t split_central_chrc_discovery_func(struct bt_conn *conn, subscribed = subscribed && slot->batt_lvl_subscribe_params.value_handle; #endif /* IS_ENABLED(CONFIG_ZMK_SPLIT_BLE_CENTRAL_BATTERY_LEVEL_FETCHING) */ + subscribed = subscribed && slot->update_layers_handle; + return subscribed ? BT_GATT_ITER_STOP : BT_GATT_ITER_CONTINUE; } @@ -865,6 +874,40 @@ int zmk_split_bt_update_hid_indicator(zmk_hid_indicators_t indicators) { #endif // IS_ENABLED(CONFIG_ZMK_SPLIT_PERIPHERAL_HID_INDICATORS) +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_bt_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 00000000000..1f1a0a78c3d --- /dev/null +++ b/app/src/split/bluetooth/peripheral_layers.c @@ -0,0 +1,15 @@ + +#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 505eb363cd8..794c3674fde 100644 --- a/app/src/split/bluetooth/service.c +++ b/app/src/split/bluetooth/service.c @@ -25,6 +25,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 @@ -138,6 +139,29 @@ static ssize_t split_svc_update_indicators(struct bt_conn *conn, const struct bt #endif // IS_ENABLED(CONFIG_ZMK_SPLIT_PERIPHERAL_HID_INDICATORS) +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; +} + BT_GATT_SERVICE_DEFINE( split_svc, BT_GATT_PRIMARY_SERVICE(BT_UUID_DECLARE_128(ZMK_SPLIT_BT_SERVICE_UUID)), BT_GATT_CHARACTERISTIC(BT_UUID_DECLARE_128(ZMK_SPLIT_BT_CHAR_POSITION_STATE_UUID), @@ -160,7 +184,9 @@ BT_GATT_SERVICE_DEFINE( BT_GATT_CHRC_WRITE_WITHOUT_RESP, BT_GATT_PERM_WRITE_ENCRYPT, NULL, split_svc_update_indicators, NULL), #endif // IS_ENABLED(CONFIG_ZMK_SPLIT_PERIPHERAL_HID_INDICATORS) -); + 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 8b07bedecb03a005360e5e26b43cf151ad7fd7bd Mon Sep 17 00:00:00 2001 From: darknao Date: Sun, 5 May 2024 18:52:44 +0200 Subject: [PATCH 11/23] 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 +++++++++ lambda/compiler.rb | 2 +- 7 files changed, 155 insertions(+), 121 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 6ef00311027..3b64ea8ea43 100644 --- a/app/CMakeLists.txt +++ b/app/CMakeLists.txt @@ -94,6 +94,7 @@ add_subdirectory(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 00000000000..0f59c89aa4e --- /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 00000000000..885c82d2de4 --- /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 00000000000..032714eb893 --- /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 3df029b2575..dc1386eb732 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) { @@ -283,12 +280,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); @@ -435,119 +427,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); - -/* - 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 +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 + }; +} - // 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; +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 - - // 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; + 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 - pixels[6] = col_red; + 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++) { + 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); + } + } +} + +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}; + } } } @@ -566,7 +484,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 00000000000..21208b1f415 --- /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; +} diff --git a/lambda/compiler.rb b/lambda/compiler.rb index 58ab0eaef8d..34ca09e9108 100644 --- a/lambda/compiler.rb +++ b/lambda/compiler.rb @@ -81,7 +81,7 @@ def compile_board(board, keymap_data:, kconfig_data:, include_static_rhs: false) end PERMITTED_DTS_SECTIONS = %w[ - behaviors macros combos conditional_layers keymap underglow-indicators + behaviors macros combos conditional_layers keymap underglow-indicators underglow-layer ].freeze def validate_devicetree!(dtsi) From 1f51e52c28fc86a816a29ab4b26d1b5c216f8dcc Mon Sep 17 00:00:00 2001 From: darknao Date: Wed, 8 May 2024 02:07:54 +0200 Subject: [PATCH 12/23] 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 3b64ea8ea43..8422190662d 100644 --- a/app/CMakeLists.txt +++ b/app/CMakeLists.txt @@ -89,6 +89,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(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 00000000000..2445c164f5c --- /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 032714eb893..ad28f74fb8a 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 00000000000..81f2ab8de00 --- /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 dc1386eb732..1b4f3d267cf 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 @@ -458,15 +459,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) { @@ -484,7 +489,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; } @@ -494,7 +499,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; } @@ -841,6 +846,15 @@ static int rgb_underglow_event_listener(const zmk_event_t *eh) { return rgb_underglow_auto_state(&prev_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) @@ -859,6 +873,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 21208b1f415..28dfe26b49a 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 fc0a7f57559..c2088ac7c43 100644 --- a/app/src/split/bluetooth/central.c +++ b/app/src/split/bluetooth/central.c @@ -29,6 +29,7 @@ LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); #include #include #include +#include static int start_scanning(void); @@ -895,6 +896,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 794c3674fde..0d87a5955e0 100644 --- a/app/src/split/bluetooth/service.c +++ b/app/src/split/bluetooth/service.c @@ -29,6 +29,7 @@ LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); #include #include +#include #if ZMK_KEYMAP_HAS_SENSORS static struct sensor_event last_sensor_event; @@ -143,7 +144,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 8ce2443d8f0cab00291bea41f6183db9a775c00a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Mart=C3=ADnez?= <58857054+elpekenin@users.noreply.github.com> Date: Thu, 25 Apr 2024 10:55:42 +0200 Subject: [PATCH 13/23] fix(rgb): auto-off logic --- app/src/rgb_underglow.c | 37 ++++++++++++++++++++++++------------- 1 file changed, 24 insertions(+), 13 deletions(-) diff --git a/app/src/rgb_underglow.c b/app/src/rgb_underglow.c index 1b4f3d267cf..e645b1a8a2b 100644 --- a/app/src/rgb_underglow.c +++ b/app/src/rgb_underglow.c @@ -823,17 +823,31 @@ 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) -static int rgb_underglow_auto_state(bool *prev_state, bool new_state) { - if (state.on == new_state) { +struct rgb_underglow_sleep_state { + bool is_awake; + bool rgb_state_before_sleeping; +}; + +static int rgb_underglow_auto_state(bool target_wake_state) { + static struct rgb_underglow_sleep_state sleep_state = { + is_awake : true, + 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; } - if (new_state) { - state.on = *prev_state; - *prev_state = false; - return zmk_rgb_underglow_on(); + sleep_state.is_awake = target_wake_state; + + if (sleep_state.is_awake) { + if (sleep_state.rgb_state_before_sleeping) { + return zmk_rgb_underglow_on(); + } else { + return zmk_rgb_underglow_off(); + } } else { - state.on = false; - *prev_state = true; + sleep_state.rgb_state_before_sleeping = sleep_state.on; return zmk_rgb_underglow_off(); } } @@ -842,9 +856,7 @@ static int rgb_underglow_event_listener(const zmk_event_t *eh) { #if IS_ENABLED(CONFIG_ZMK_RGB_UNDERGLOW_AUTO_OFF_IDLE) if (as_zmk_activity_state_changed(eh)) { - static bool prev_state = false; - return rgb_underglow_auto_state(&prev_state, - zmk_activity_get_state() == ZMK_ACTIVITY_ACTIVE); + 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); @@ -859,8 +871,7 @@ static int rgb_underglow_event_listener(const zmk_event_t *eh) { #if IS_ENABLED(CONFIG_ZMK_RGB_UNDERGLOW_AUTO_OFF_USB) if (as_zmk_usb_conn_state_changed(eh)) { - static bool prev_state = false; - return rgb_underglow_auto_state(&prev_state, zmk_usb_is_powered()); + return rgb_underglow_auto_state(zmk_usb_is_powered()); } #endif From 428324c448ca2568e3c7017831cd8c7d7a213866 Mon Sep 17 00:00:00 2001 From: Jarryd Tilbrook Date: Thu, 25 Apr 2024 22:26:26 +0800 Subject: [PATCH 14/23] fix(underglow): Correctly set underglow state This fixes a bug introduced in #2244 --- app/src/rgb_underglow.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/rgb_underglow.c b/app/src/rgb_underglow.c index e645b1a8a2b..abe00c07128 100644 --- a/app/src/rgb_underglow.c +++ b/app/src/rgb_underglow.c @@ -847,7 +847,7 @@ static int rgb_underglow_auto_state(bool target_wake_state) { return zmk_rgb_underglow_off(); } } else { - sleep_state.rgb_state_before_sleeping = sleep_state.on; + sleep_state.rgb_state_before_sleeping = state.on; return zmk_rgb_underglow_off(); } } From ec9811544c4b19b0f04802016387b00520798eed Mon Sep 17 00:00:00 2001 From: darknao Date: Sun, 12 May 2024 23:11:25 +0200 Subject: [PATCH 15/23] 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 | 28 +++++++++++++++---- app/src/rgb_underglow_layer.c | 20 +++++++++---- app/src/split/bluetooth/peripheral_layers.c | 14 +++++++++- 5 files changed, 54 insertions(+), 15 deletions(-) diff --git a/app/include/zmk/rgb_underglow_layer.h b/app/include/zmk/rgb_underglow_layer.h index ad28f74fb8a..3681a97e005 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 e816cad0081..974a322c9c0 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 abe00c07128..b9a8e775078 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) @@ -428,6 +432,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){ @@ -473,6 +479,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) { @@ -489,7 +496,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; } @@ -842,6 +849,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(); @@ -858,16 +868,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); } +#endif + +#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); + 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); +#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)) { @@ -884,8 +900,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 28dfe26b49a..fb9876cf144 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 1f1a0a78c3d..243b4f20421 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; @@ -12,4 +13,15 @@ void set_peripheral_layers_state(uint32_t new_layers) { 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 2a22d165b76868d46759b851130485a244a98592 Mon Sep 17 00:00:00 2001 From: darknao Date: Mon, 13 May 2024 14:43:08 +0200 Subject: [PATCH 16/23] 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 0c45e1c68f7..f00dcd5e257 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 8f421f85d02..98d102b3eb2 100644 --- a/app/src/activity.c +++ b/app/src/activity.c @@ -18,6 +18,7 @@ LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); #include #include #include +#include #include #include @@ -160,5 +161,6 @@ 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); SYS_INIT(activity_init, APPLICATION, CONFIG_APPLICATION_INIT_PRIORITY); diff --git a/app/src/rgb_underglow.c b/app/src/rgb_underglow.c index b9a8e775078..f0a8ed66ee2 100644 --- a/app/src/rgb_underglow.c +++ b/app/src/rgb_underglow.c @@ -466,18 +466,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 */ @@ -506,7 +505,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; } @@ -640,6 +639,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; @@ -649,7 +653,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) { @@ -662,6 +666,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; @@ -671,7 +680,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) { @@ -848,17 +857,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 d504e844fe8b5dd37b20fcd1460464ea628cca73 Mon Sep 17 00:00:00 2001 From: darknao Date: Mon, 20 May 2024 19:51:09 +0200 Subject: [PATCH 17/23] 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 f0a8ed66ee2..69a2f7113ef 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; }; @@ -466,7 +467,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); @@ -604,7 +605,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; } @@ -640,6 +641,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(); } @@ -667,6 +671,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(); } @@ -697,7 +702,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 6585434c6bd14c03a13686e39345f08d670da4fa Mon Sep 17 00:00:00 2001 From: Nick Winans Date: Fri, 6 May 2022 00:19:08 -0500 Subject: [PATCH 18/23] 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_listener.c | 87 ++++++++++++++++++++++ 3 files changed, 108 insertions(+) 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 d448c0e5a5a..229bd5cec4a 100644 --- a/app/src/split/bluetooth/CMakeLists.txt +++ b/app/src/split/bluetooth/CMakeLists.txt @@ -10,6 +10,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 4da50528343..7b32a524d0c 100644 --- a/app/src/split/bluetooth/Kconfig +++ b/app/src/split/bluetooth/Kconfig @@ -70,6 +70,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_listener.c b/app/src/split/bluetooth/central_listener.c new file mode 100644 index 00000000000..87e471cb776 --- /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 8d196f949c8d596a021593e6f628f88210caad5c Mon Sep 17 00:00:00 2001 From: darknao Date: Wed, 22 May 2024 13:19:28 +0200 Subject: [PATCH 19/23] 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 69a2f7113ef..bba917baadf 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) @@ -433,7 +433,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; @@ -479,7 +479,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) { @@ -843,7 +843,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; @@ -862,7 +862,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 @@ -886,7 +886,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); @@ -912,17 +912,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 9e9219da9aba9f7c2ab8401a78827a5d525ceac9 Mon Sep 17 00:00:00 2001 From: Chris Andreae Date: Thu, 23 May 2024 11:55:28 +0900 Subject: [PATCH 20/23] Compiler service: pass keymap data when building RHS --- lambda/compiler.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lambda/compiler.rb b/lambda/compiler.rb index 34ca09e9108..2e94c6531db 100644 --- a/lambda/compiler.rb +++ b/lambda/compiler.rb @@ -19,8 +19,8 @@ def initialize(message, status: 400, log:) def compile(keymap_data, lhs_kconfig_data, rhs_kconfig_data) if rhs_kconfig_data && !rhs_kconfig_data.empty? - lhs_result, lhs_output = compile_board('glove80_lh', keymap_data:, kconfig_data: lhs_kconfig_data, include_static_rhs: false) - rhs_result, rhs_output = compile_board('glove80_rh', keymap_data: nil, kconfig_data: rhs_kconfig_data, include_static_rhs: false) + lhs_result, lhs_output = compile_board('glove80_lh', keymap_data:, kconfig_data: lhs_kconfig_data, include_static_rhs: false) + rhs_result, rhs_output = compile_board('glove80_rh', keymap_data:, kconfig_data: rhs_kconfig_data, include_static_rhs: false) [ lhs_result.concat(rhs_result), ["LHS Output:", *lhs_output, "RHS Output:", *rhs_output], From 169afd4eb6c39642afb81c18550dd5f7948f6067 Mon Sep 17 00:00:00 2001 From: darknao Date: Thu, 23 May 2024 16:09:11 +0200 Subject: [PATCH 21/23] underglow-layer: enable with EXPERIMENTAL_RGB_LAYER Kconfig --- app/Kconfig | 4 ++++ app/src/rgb_underglow.c | 4 +++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/app/Kconfig b/app/Kconfig index f90b1d2ab57..f27c1689362 100644 --- a/app/Kconfig +++ b/app/Kconfig @@ -344,6 +344,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 + #ZMK_RGB_UNDERGLOW endif diff --git a/app/src/rgb_underglow.c b/app/src/rgb_underglow.c index bba917baadf..c860dc32756 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 }; From 5b327c270ee811df2e8e3710dfc5c68a70f62fb2 Mon Sep 17 00:00:00 2001 From: darknao Date: Sat, 25 May 2024 12:28:23 +0200 Subject: [PATCH 22/23] fix build if EXPERIMENTAL_RGB_LAYER not set --- app/src/rgb_underglow.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/app/src/rgb_underglow.c b/app/src/rgb_underglow.c index c860dc32756..1e3776b7449 100644 --- a/app/src/rgb_underglow.c +++ b/app/src/rgb_underglow.c @@ -497,9 +497,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(); @@ -508,7 +505,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; } @@ -643,9 +640,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(); } @@ -704,7 +703,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 6d82f4819c71df9fea7b8e4f7936c60f4b6a50b4 Mon Sep 17 00:00:00 2001 From: darknao Date: Sat, 25 May 2024 13:30:49 +0200 Subject: [PATCH 23/23] 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 1e3776b7449..b979747a040 100644 --- a/app/src/rgb_underglow.c +++ b/app/src/rgb_underglow.c @@ -446,7 +446,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, @@ -457,6 +457,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) { @@ -464,8 +465,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) { @@ -473,12 +477,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) */